[Dynect] Extract Dynect DNS provider to fog-dynect.

This commit is contained in:
Glenn Pratt 2015-08-06 09:48:17 -07:00
parent f99adc8907
commit 1d2700570e
20 changed files with 1 additions and 1321 deletions

View File

@ -55,6 +55,7 @@ Gem::Specification.new do |s|
s.add_dependency("fog-atmos")
s.add_dependency("fog-aws", ">= 0.6.0")
s.add_dependency("fog-brightbox", "~> 0.4")
s.add_dependency("fog-dynect")
s.add_dependency("fog-ecloud", "= 0.1.1")
s.add_dependency("fog-google", ">= 0.0.2")
s.add_dependency("fog-local")

View File

@ -1 +0,0 @@
require 'fog/dynect/dns'

View File

@ -1,26 +0,0 @@
require 'nokogiri'
require 'fog/core'
require 'fog/xml'
module Fog
module Dynect
extend Fog::Provider
service(:dns, 'DNS')
class Mock
def self.job_id
Fog::Mock.random_numbers(8).to_i
end
def self.token
Fog::Mock.random_hex(48)
end
def self.version
[Fog::Mock.random_numbers(1), Fog::Mock.random_numbers(1), Fog::Mock.random_numbers(1)].join('.')
end
end
end
end

View File

@ -1,157 +0,0 @@
require 'fog/dynect/core'
module Fog
module DNS
class Dynect < Fog::Service
requires :dynect_customer, :dynect_username, :dynect_password
recognizes :timeout, :persistent, :job_poll_timeout
recognizes :provider # remove post deprecation
model_path 'fog/dynect/models/dns'
model :record
collection :records
model :zone
collection :zones
request_path 'fog/dynect/requests/dns'
request :delete_record
request :delete_zone
request :get_node_list
request :get_all_records
request :get_record
request :get_zone
request :post_record
request :post_session
request :post_zone
request :put_zone
request :put_record
class JobIncomplete < Error; end
class Mock
def initialize(options={})
@dynect_customer = options[:dynect_customer]
@dynect_username = options[:dynect_username]
@dynect_password = options[:dynect_password]
end
def self.data
@data ||= {
:zones => {}
}
end
def self.reset
@data = nil
end
def auth_token
@auth_token ||= Fog::Dynect::Mock.token
end
def data
self.class.data
end
def reset_data
self.class.reset
end
end
class Real
def initialize(options={})
@dynect_customer = options[:dynect_customer]
@dynect_username = options[:dynect_username]
@dynect_password = options[:dynect_password]
@connection_options = options[:connection_options] || {}
@host = 'api-v4.dynect.net'
@port = options[:port] || 443
@path = options[:path] || '/REST'
@persistent = options[:persistent] || false
@scheme = options[:scheme] || 'https'
@version = options[:version] || '3.5.2'
@job_poll_timeout = options[:job_poll_timeout] || 10
@connection = Fog::XML::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options)
end
def auth_token
@auth_token ||= post_session.body['data']['token']
end
def request(params)
begin
# any request could redirect to a job
params[:expects] = Array(params[:expects]) | [307]
params[:headers] ||= {}
params[:headers]['Content-Type'] = 'application/json'
params[:headers]['API-Version'] = @version
params[:headers]['Auth-Token'] = auth_token unless params[:path] == 'Session'
params[:path] = "#{@path}/#{params[:path]}" unless params[:path] =~ %r{^#{Regexp.escape(@path)}/}
response = @connection.request(params)
if response.body.empty?
response.body = {}
elsif response.headers['Content-Type'] == 'application/json'
response.body = Fog::JSON.decode(response.body)
end
if response.body['status'] == 'failure'
raise Error, response.body['msgs'].first['INFO']
end
if params[:path] !~ %r{^/REST/Job/}
if response.status == 307
response = poll_job(response, params[:expects], @job_poll_timeout)
# Dynect intermittently returns 200 with an incomplete status. When this
# happens, the job should still be polled.
elsif response.status == 200 && response.body['status'].eql?('incomplete')
response.headers['Location'] = "/REST/Job/#{ response.body['job_id'] }"
response = poll_job(response, params[:expects], @job_poll_timeout)
end
end
response
rescue Excon::Errors::HTTPStatusError => error
if @auth_token && error.message =~ /login: (Bad or expired credentials|inactivity logout)/
@auth_token = nil
retry
else
raise error
end
end
response
end
def poll_job(response, original_expects, time_to_wait)
job_location = response.headers['Location']
begin
Fog.wait_for(time_to_wait) do
response = request(
:expects => original_expects,
:idempotent => true,
:method => :get,
:path => job_location
)
response.body['status'] != 'incomplete'
end
rescue Errors::TimeoutError => error
if response.body['status'] == 'incomplete'
raise JobIncomplete.new("Job #{response.body['job_id']} is still incomplete")
else
raise error
end
end
response
end
end
end
end
end

View File

@ -1,67 +0,0 @@
require 'fog/core/model'
module Fog
module DNS
class Dynect
class Record < Fog::Model
extend Fog::Deprecation
identity :id
attribute :name, :aliases => [:fqdn, 'fqdn']
attribute :rdata
attribute :serial_style
attribute :ttl
attribute :type, :aliases => 'record_type'
def destroy
requires :identity, :name, :type, :zone
service.delete_record(type, zone.identity, name, identity)
true
end
def save(replace=false)
requires :name, :type, :rdata, :zone
options = {
:ttl => ttl
}
options.delete_if {|key, value| value.nil?}
if replace
data = service.put_record(type, zone.identity, name, rdata, options).body['data']
else
data = service.post_record(type, zone.identity, name, rdata, options).body['data']
end
# avoid overwriting zone object with zone string
data = data.reject {|key, value| key == 'zone'}
merge_attributes(data)
zone.publish
records = service.get_record(type, zone.identity, name).body['data']
# data in format ['/REST/xRecord/domain/fqdn/identity]
records.map! do |record|
tokens = record.split('/')
{
:identity => tokens.last,
:type => tokens[2][0...-6] # everything before 'Record'
}
end
record = records.find {|record| record[:type] == type}
merge_attributes(record)
true
end
def zone
@zone
end
private
def zone=(new_zone)
@zone = new_zone
end
end
end
end
end

View File

@ -1,48 +0,0 @@
require 'fog/core/collection'
require 'fog/dynect/models/dns/record'
module Fog
module DNS
class Dynect
class Records < Fog::Collection
attribute :zone
model Fog::DNS::Dynect::Record
def all(options = {})
requires :zone
data = []
service.get_all_records(zone.domain, options).body['data'].each do |records|
(type, list) = records
list.each do |record|
data << {
:identity => record['record_id'],
:fqdn => record['fqdn'],
:type => record['record_type'],
:rdata => record['rdata']
}
end
end
load(data)
end
def get(record_id)
requires :zone
# there isn't a way to look up by just id
# must have type and domain for 'get_record' request
# so we pick it from the list returned by 'all'
list = all
list.detect {|e| e.id == record_id}
end
def new(attributes = {})
requires :zone
super({:zone => zone}.merge!(attributes))
end
end
end
end
end

View File

@ -1,56 +0,0 @@
require 'fog/core/model'
require 'fog/dynect/models/dns/records'
module Fog
module DNS
class Dynect
class Zone < Fog::Model
identity :domain
attribute :domain, :aliases => 'zone'
attribute :email, :aliases => 'rname'
attribute :serial
attribute :serial_style
attribute :ttl
attribute :type, :aliases => 'zone_type'
def initialize(attributes={})
super
end
def destroy
requires :domain
service.delete_zone(domain)
true
end
undef_method :domain=
def domain=(new_domain)
attributes[:domain] = new_domain.split('/').last
end
def publish
requires :identity
data = service.put_zone(identity, 'publish' => true)
true
end
def records
@records ||= Fog::DNS::Dynect::Records.new(:zone => self, :service => service)
end
def nameservers
raise 'nameservers Not Implemented'
end
def save
self.ttl ||= 3600
requires :domain, :email, :ttl
data = service.post_zone(email, ttl, domain).body['data']
merge_attributes(data)
true
end
end
end
end
end

View File

@ -1,25 +0,0 @@
require 'fog/core/collection'
require 'fog/dynect/models/dns/zone'
module Fog
module DNS
class Dynect
class Zones < Fog::Collection
model Fog::DNS::Dynect::Zone
def all
data = service.get_zone.body['data'].map do |zone|
{ :domain => zone }
end
load(data)
end
def get(zone_id)
new(service.get_zone('zone' => zone_id).body['data'])
rescue Excon::Errors::NotFound
nil
end
end
end
end
end

View File

@ -1,55 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Delete a record
#
# ==== Parameters
# * type<~String> - type of record in ['AAAA', 'ANY', 'A', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'LOC', 'MX', 'NSA', 'NS', 'PTR', 'PX', 'RP', 'SOA', 'SPF', 'SRV', 'SSHFP', 'TXT']
# * zone<~String> - zone of record
# * fqdn<~String> - fqdn of record
# * record_id<~String> - id of record
def delete_record(type, zone, fqdn, record_id)
request(
:expects => 200,
:idempotent => true,
:method => :delete,
:path => ["#{type.to_s.upcase}Record", zone, fqdn, record_id].join('/')
)
end
end
class Mock
def delete_record(type, zone, fqdn, record_id)
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
raise Fog::DNS::Dynect::NotFound unless zone[:records][type].find { |record| record[:fqdn] == fqdn && record[:record_id] == record_id.to_i }
zone[:records_to_delete] << {
:type => type,
:fqdn => fqdn,
:record_id => record_id.to_i
}
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => {},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => "delete: Record will be deleted on zone publish",
"SOURCE" => "BLL",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,41 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Delete a zone
#
# ==== Parameters
# * zone<~String> - zone to host
def delete_zone(zone)
request(
:expects => 200,
:method => :delete,
:path => "Zone/#{zone}"
)
end
end
class Mock
def delete_zone(zone)
self.data[:zones].delete(zone)
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => {},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"ERR_CD" => '',
"INFO" => '',
"LVL" => '',
"SOURCE" => ''
}]
}
response
end
end
end
end
end

View File

@ -1,56 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Get one or more node lists
#
# ==== Parameters
# * zone<~String> - zone to lookup node lists for
# * options<~Hash>
# * fqdn<~String> - fully qualified domain name of node to lookup
def get_all_records(zone, options = {})
requested_fqdn = options['fqdn'] || options[:fqdn]
request(
:expects => 200,
:idempotent => true,
:method => :get,
:path => ['AllRecord', zone, requested_fqdn].compact.join('/'),
:query => {'detail' => 'Y'} # return full records, instead of just resource URLs
)
end
end
class Mock
def get_all_records(zone, options = {})
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
response = Excon::Response.new
response.status = 200
data = [zone[:zone]]
if fqdn = options[:fqdn]
data = data | zone[:records].map { |type, records| records.select { |record| record[:fqdn] == fqdn } }.flatten.compact
else
data = data | zone[:records].map { |type, records| records.map { |record| record[:fqdn] } }.flatten
end
response.body = {
"status" => "success",
"data" => data,
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => "get_tree: Here is your zone tree",
"SOURCE" => "BLL",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,55 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Get one or more node lists
#
# ==== Parameters
# * zone<~String> - zone to lookup node lists for
# * options<~Hash>
# * fqdn<~String> - fully qualified domain name of node to lookup
def get_node_list(zone, options = {})
requested_fqdn = options['fqdn'] || options[:fqdn]
request(
:expects => 200,
:idempotent => true,
:method => :get,
:path => ['AllRecord', zone, requested_fqdn].compact.join('/')
)
end
end
class Mock
def get_node_list(zone, options = {})
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
response = Excon::Response.new
response.status = 200
data = [zone[:zone]]
if fqdn = options[:fqdn]
data = data | zone[:records].map { |type, records| records.select { |record| record[:fqdn] == fqdn } }.flatten.compact
else
data = data | zone[:records].map { |type, records| records.map { |record| record[:fqdn] } }.flatten
end
response.body = {
"status" => "success",
"data" => data,
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => "get_tree: Here is your zone tree",
"SOURCE" => "BLL",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,83 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# List records of a given type
#
# ==== Parameters
# * type<~String> - type of record in ['AAAA', 'ANY', 'A', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'LOC', 'MX', 'NSA', 'NS', 'PTR', 'PX', 'RP', 'SOA', 'SPF', 'SRV', 'SSHFP', 'TXT']
# * zone<~String> - name of zone to lookup
# * fqdn<~String> - name of fqdn to lookup
# * options<~Hash>:
# * record_id<~String> - id of record
def get_record(type, zone, fqdn, options = {})
request(
:expects => 200,
:idempotent => true,
:method => :get,
:path => ["#{type.to_s.upcase}Record", zone, fqdn, options['record_id']].compact.join('/')
)
end
end
class Mock
def get_record(type, zone, fqdn, options = {})
raise ArgumentError unless [
'AAAA', 'ANY', 'A', 'CNAME',
'DHCID', 'DNAME', 'DNSKEY',
'DS', 'KEY', 'LOC', 'MX',
'NSA', 'NS', 'PTR', 'PX',
'RP', 'SOA', 'SPF', 'SRV',
'SSHFP', 'TXT'
].include? type
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
response = Excon::Response.new
response.status = 200
if record_id = options['record_id']
raise Fog::DNS::Dynect::NotFound unless record = zone[:records][type].find { |record| record[:record_id] == record_id.to_i }
response.body = {
"status" => "success",
"data" => {
"zone" => record[:zone][:zone],
"ttl" => record[:ttl],
"fqdn" => record[:fqdn],
"record_type" => type,
"rdata" => record[:rdata],
"record_id" => record[:record_id]
},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => "get: Found the record",
"SOURCE" => "API-B",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
else
records = if type == "ANY"
zone[:records].values.flatten.select { |record| record[:fqdn] == fqdn }
else
zone[:records][type].select { |record| record[:fqdn] == fqdn }
end
response.body = {
"status" => "success",
"data" => records.map { |record| "/REST/#{record[:type]}Record/#{record[:zone][:zone]}/#{record[:fqdn]}/#{record[:record_id]}" },
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => "detail: Found #{records.size} record",
"SOURCE" => "BLL",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
end
response
end
end
end
end
end

View File

@ -1,57 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Get one or more zones
#
# ==== Parameters
# * options<~Hash>:
# * zone<~String> - name of zone to lookup, or omit to return list of zones
def get_zone(options = {})
request(
:expects => 200,
:idempotent => true,
:method => :get,
:path => ['Zone', options['zone']].compact.join('/')
)
end
end
class Mock
def get_zone(options = {})
if options['zone']
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][options['zone']]
data = {
"zone_type" => zone[:zone_type],
"serial_style" => zone[:serial_style],
"serial" => zone[:serial],
"zone" => zone[:zone]
}
info = "get: Your zone, #{zone[:zone]}"
else
data = self.data[:zones].map { |zone, data| "/REST/Zone/#{zone}/" }
info = "get: Your #{data.size} zones"
end
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => data,
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => info,
"SOURCE" => "BLL",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,71 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Create a record
#
# ==== Parameters
# * type<~String> - type of record in ['AAAA', 'ANY', 'A', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'LOC', 'MX', 'NSA', 'NS', 'PTR', 'PX', 'RP', 'SOA', 'SPF', 'SRV', 'SSHFP', 'TXT']
# * zone<~String> - zone of record
# * rdata<~Hash> - rdata for record
# * options<~Hash>: (options vary by type, listing below includes common parameters)
# * ttl<~Integer> - ttl for the record, defaults to zone ttl
def post_record(type, zone, fqdn, rdata, options = {})
options.merge!('rdata' => rdata)
request(
:body => Fog::JSON.encode(options),
:expects => 200,
:method => :post,
:path => ["#{type.to_s.upcase}Record", zone, fqdn].join('/')
)
end
end
class Mock
def post_record(type, zone, fqdn, rdata, options = {})
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
records = zone[:records]
record_id = zone[:next_record_id]
zone[:next_record_id] += 1
record = {
:type => type,
:zone => zone,
:fqdn => fqdn,
:rdata => rdata,
:ttl => options[:ttl] || zone[:ttl],
:record_id => record_id
}
records[type] << record
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => {
"zone" => record[:zone][:zone],
"ttl" => record[:ttl],
"fqdn" => record[:fqdn],
"record_type" => record[:type],
"rdata" => record[:rdata],
"record_id" => record[:record_id]
},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO"=>"add: Record added",
"SOURCE"=>"BLL",
"ERR_CD"=>nil,
"LVL"=>"INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,43 +0,0 @@
module Fog
module DNS
class Dynect
class Real
def post_session
request(
:expects => 200,
:idempotent => true,
:method => :post,
:path => "Session",
:body => Fog::JSON.encode({
:customer_name => @dynect_customer,
:user_name => @dynect_username,
:password => @dynect_password
})
)
end
end
class Mock
def post_session
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => {
"token" => auth_token,
"version" => Fog::Dynect::Mock.version
},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs"=>[{
"INFO"=>"login: Login successful",
"SOURCE"=>"BLL",
"ERR_CD"=>nil,
"LVL"=>"INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,70 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Create a zone
#
# ==== Parameters
# * rname<~String> - administrative contact
# * ttl<~Integer> - time to live (in seconds) for records in this zone
# * zone<~String> - name of zone to host
# * options<~Hash>:
# * serial_style<~String> - style of serial number, in ['day', 'epoch', 'increment', 'minute']. Defaults to increment
def post_zone(rname, ttl, zone, options = {})
body = Fog::JSON.encode({
:rname => rname,
:token => auth_token,
:ttl => ttl
}.merge!(options))
request(
:body => body,
:expects => 200,
:method => :post,
:path => 'Zone/' << zone
)
end
end
class Mock
def post_zone(rname, ttl, zone, options = {})
new_zone = self.data[:zones][zone] = {
:next_record_id => 0,
:records => Hash.new do |records_hash, type|
records_hash[type] = []
end,
:records_to_delete => [],
:rname => rname,
:serial_style => options[:serial_style] || "increment",
:serial => 0,
:ttl => ttl,
:zone => zone,
:zone_type => "Primary"
}
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => {
"zone_type" => new_zone[:zone_type],
"serial_style" => new_zone[:serial_style],
"serial" => new_zone[:serial],
"zone" => zone
},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => "create: New zone #{zone} created. Publish it to put it on our server.",
"SOURCE" => "BLL",
"ERR_CD" => nil,
"LVL" => "INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,76 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Update or replace a record
#
# ==== Parameters
# * type<~String> - type of record in ['AAAA', 'ANY', 'A', 'CNAME', 'DHCID', 'DNAME', 'DNSKEY', 'DS', 'KEY', 'LOC', 'MX', 'NSA', 'NS', 'PTR', 'PX', 'RP', 'SOA', 'SPF', 'SRV', 'SSHFP', 'TXT']
# * zone<~String> - zone of record
# * rdata<~Hash> - rdata for record
# * options<~Hash>: (options vary by type, listing below includes common parameters)
# * ttl<~Integer> - ttl for the record, defaults to zone ttl
def put_record(type, zone, fqdn, rdata, options = {})
options.merge!('rdata' => rdata)
type.to_s.upcase!
options = {"#{type}Records" => [options]} unless options['record_id']
path = ["#{type}Record", zone, fqdn].join('/')
path += "/#{options.delete('record_id')}" if options['record_id']
request(
:body => Fog::JSON.encode(options),
:expects => 200,
:idempotent => true,
:method => :put,
:path => path
)
end
end
class Mock
def put_record(type, zone, fqdn, rdata, options = {})
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
records = zone[:records]
record_id = zone[:next_record_id]
zone[:next_record_id] += 1
record = {
:type => type,
:zone => zone,
:fqdn => fqdn,
:rdata => rdata,
:ttl => options[:ttl] || zone[:ttl],
:record_id => record_id
}
records[type] << record
response = Excon::Response.new
response.status = 200
response.body = {
"status" => "success",
"data" => {
"zone" => record[:zone][:zone],
"ttl" => record[:ttl],
"fqdn" => record[:fqdn],
"record_type" => record[:type],
"rdata" => record[:rdata],
"record_id" => record[:record_id]
},
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO"=>"add: Record added",
"SOURCE"=>"BLL",
"ERR_CD"=>nil,
"LVL"=>"INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,76 +0,0 @@
module Fog
module DNS
class Dynect
class Real
# Update a zone
#
# ==== Parameters
# * zone<~String> - name or id of zone
# * options<~Hash>:
# * freeze<~Boolean> - causes zone to become frozen
# * publish<~Boolean> - causes all pending changes to be pushed to nameservers
# * thaw<~Boolean> - causes zone to cease being frozen
def put_zone(zone, options = {})
request(
:body => Fog::JSON.encode(options),
:expects => 200,
:idempotent => true,
:method => :put,
:path => 'Zone/' << zone
)
end
end
class Mock
def put_zone(zone, options = {})
raise Fog::DNS::Dynect::NotFound unless zone = self.data[:zones][zone]
raise ArgumentError unless options.size == 1
response = Excon::Response.new
response.status = 200
data = {}
if options['freeze']
zone['frozen'] = true
info = "freeze: Your zone is now frozen"
elsif options['publish']
zone[:changes] = {}
zone[:records_to_delete].each do |record|
zone[:records][record[:type]].delete_if { |r| r[:fqdn] == record[:fqdn] && r[:record_id] == record[:record_id] }
end
zone[:records_to_delete] = []
data = {
"zone_type" => zone[:zone_type],
"serial_style" => zone[:serial_style],
"serial" => zone[:serial] += 1,
"zone" => zone[:zone]
}
info = "publish: #{zone[:zone]} published"
elsif options['thaw']
zone[:frozen] = false
info = "thaw: Your zone is now thawed, you may edit normally"
else
raise ArgumentError
end
response.body = {
"status" => "success",
"data" => data,
"job_id" => Fog::Dynect::Mock.job_id,
"msgs" => [{
"INFO" => info,
"SOURCE"=>"BLL",
"ERR_CD"=>nil,
"LVL"=>"INFO"
}]
}
response
end
end
end
end
end

View File

@ -1,258 +0,0 @@
Shindo.tests('Dynect::dns | DNS requests', ['dynect', 'dns']) do
shared_format = {
'job_id' => Integer,
'msgs' => [{
'ERR_CD' => Fog::Nullable::String,
'INFO' => String,
'LVL' => String,
'SOURCE' => String
}],
'status' => String
}
tests "success" do
@dns = Fog::DNS[:dynect]
@domain = generate_unique_domain
@fqdn = "www.#{@domain}"
post_session_format = shared_format.merge({
'data' => {
'token' => String,
'version' => String
}
})
tests("post_session").formats(post_session_format) do
@dns.post_session.body
end
post_zone_format = shared_format.merge({
'data' => {
'serial' => Integer,
'zone' => String,
'zone_type' => String,
'serial_style' => String
}
})
tests("post_zone('netops@#{@domain}', 3600, '#{@domain}')").formats(post_zone_format) do
@dns.post_zone("netops@#{@domain}", 3600, @domain).body
end
get_zones_format = shared_format.merge({
'data' => [String]
})
tests("get_zone").formats(get_zones_format) do
@dns.get_zone.body
end
get_zone_format = shared_format.merge({
'data' => {
"serial" => Integer,
"serial_style" => String,
"zone" => String,
"zone_type" => String
}
})
tests("get_zone('zone' => '#{@domain}')").formats(get_zone_format) do
@dns.get_zone('zone' => @domain).body
end
post_record_format = shared_format.merge({
'data' => {
'fqdn' => String,
'rdata' => {
'address' => String
},
'record_id' => Integer,
'record_type' => String,
'ttl' => Integer,
'zone' => String
}
})
tests("post_record('A', '#{@domain}', '#{@fqdn}', 'address' => '1.2.3.4')").formats(post_record_format) do
@dns.post_record('A', @domain, @fqdn, {'address' => '1.2.3.4'}).body
end
put_record_format = shared_format.merge({
'data' => {
'fqdn' => String,
'ARecords' => [
{
'rdata' => {
'address' => String
}
}
],
'record_id' => Integer,
'record_type' => String,
'ttl' => Integer,
'zone' => String
}
})
tests("put_record('A', '#{@domain}', '#{@fqdn}', 'address' => '1.2.3.4')").formats(post_record_format) do
@dns.put_record('A', @domain, @fqdn, {'address' => '1.2.3.4'}).body
end
publish_zone_format = shared_format.merge({
'data' => {
'serial' => Integer,
'serial_style' => String,
'zone' => String,
'zone_type' => String
}
})
tests("put_zone('#{@domain}', 'publish' => true)").formats(publish_zone_format) do
@dns.put_zone(@domain, 'publish' => true).body
end
freeze_zone_format = shared_format.merge({
'data' => {}
})
tests("put_zone('#{@domain}', 'freeze' => true)").formats(freeze_zone_format) do
@dns.put_zone(@domain, 'freeze' => true).body
end
thaw_zone_format = shared_format.merge({
'data' => {}
})
tests("put_zone('#{@domain}', 'thaw' => true)").formats(thaw_zone_format) do
@dns.put_zone(@domain, 'thaw' => true).body
end
get_node_list_format = shared_format.merge({
'data' => [String]
})
tests("get_node_list('#{@domain}')").formats(get_node_list_format) do
@dns.get_node_list(@domain).body
end
get_all_records_format = shared_format.merge({
'data' => [String]
})
tests("get_all_records('#{@domain}')").formats(get_all_records_format) do
@dns.get_all_records(@domain).body
end
get_records_format = shared_format.merge({
'data' => [String]
})
tests("get_record('A', '#{@domain}', '#{@fqdn}')").formats(get_records_format) do
data = @dns.get_record('A', @domain, @fqdn).body
@record_id = data['data'].first.split('/').last
data
end
sleep 5 unless Fog.mocking?
@dns.post_record('CNAME', @domain, "cname.#{@fqdn}", {'cname' => "#{@fqdn}."})
tests("get_record('ANY', '#{@domain}', 'cname.#{@fqdn}')").formats(get_records_format) do
@dns.get_record('ANY', @domain, "cname.#{@fqdn}").body
end
get_record_format = shared_format.merge({
'data' => {
'zone' => String,
'ttl' => Integer,
'fqdn' => String,
'record_type' => String,
'rdata' => {
'address' => String
},
'record_id' => Integer
}
})
tests("get_record('A', '#{@domain}', '#{@fqdn}', 'record_id' => '#{@record_id}')").formats(get_record_format) do
@dns.get_record('A', @domain, @fqdn, 'record_id' => @record_id).body
end
delete_record_format = shared_format.merge({
'data' => {}
})
tests("delete_record('A', '#{@domain}', '#{@fqdn}', '#{@record_id}')").formats(delete_record_format) do
@dns.delete_record('A', @domain, "#{@fqdn}", @record_id).body
end
delete_zone_format = shared_format.merge({
'data' => {}
})
sleep 5 unless Fog.mocking?
tests("delete_zone('#{@domain}')").formats(delete_zone_format) do
@dns.delete_zone(@domain).body
end
tests("job handling") do
pending unless Fog.mocking?
old_mock_value = Excon.defaults[:mock]
Excon.stubs.clear
tests("returns final response from a complete job").returns({"status" => "success"}) do
begin
Excon.defaults[:mock] = true
Excon.stub({:method => :post, :path => "/REST/Session"}, {:body=>"{\"status\": \"success\", \"data\": {\"token\": \"foobar\", \"version\": \"2.3.1\"}, \"job_id\": 150583906, \"msgs\": [{\"INFO\": \"login: Login successful\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}", :headers=>{"Content-Type"=>"application/json"}, :status=>200})
Excon.stub({:method => :get, :path => "/REST/Zone/example.com"}, {:status => 307, :body => '/REST/Job/150576635', :headers => {'Content-Type' => 'text/html', 'Location' => '/REST/Job/150576635'}})
Excon.stub({:method => :get, :path => "/REST/Job/150576635"}, {:status => 307, :body => '{"status":"success"}', :headers => {'Content-Type' => 'application/json'}})
Fog::DNS::Dynect::Real.new.request(:method => :get, :path => "Zone/example.com").body
ensure
Excon.stubs.clear
Excon.defaults[:mock] = old_mock_value
end
end
tests("passes expects through when polling a job").returns({"status" => "success"}) do
begin
Excon.defaults[:mock] = true
Excon.stub({:method => :post, :path => "/REST/Session"}, {:body=>"{\"status\": \"success\", \"data\": {\"token\": \"foobar\", \"version\": \"2.3.1\"}, \"job_id\": 150583906, \"msgs\": [{\"INFO\": \"login: Login successful\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}", :headers=>{"Content-Type"=>"application/json"}, :status=>200})
Excon.stub({:method => :get, :path => "/REST/Zone/example.com"}, {:status => 307, :body => '/REST/Job/150576635', :headers => {'Content-Type' => 'text/html', 'Location' => '/REST/Job/150576635'}})
Excon.stub({:method => :get, :path => "/REST/Job/150576635"}, {:status => 404, :body => '{"status":"success"}', :headers => {'Content-Type' => 'application/json'}})
Fog::DNS::Dynect::Real.new.request(:method => :get, :expects => 404, :path => "Zone/example.com").body
ensure
Excon.stubs.clear
Excon.defaults[:mock] = old_mock_value
end
end
end
end
tests('failure') do
tests("#auth_token with expired credentials").raises(Excon::Errors::BadRequest) do
pending if Fog.mocking?
@dns = Fog::DNS[:dynect]
@dns.instance_variable_get(:@connection).stub(:request) { raise Excon::Errors::BadRequest.new('Expected(200) <=> Actual(400 Bad Request) request => {:headers=>{"Content-Type"=>"application/json", "API-Version"=>"2.3.1", "Auth-Token"=>"auth-token", "Host"=>"api2.dynect.net:443", "Content-Length"=>0}, :host=>"api2.dynect.net", :mock=>nil, :path=>"/REST/CNAMERecord/domain.com/www.domain.com", :port=>"443", :query=>nil, :scheme=>"https", :expects=>200, :method=>:get} response => #<Excon::Response:0x00000008478b98 @body="{"status": "failure", "data": {}, "job_id": 21326025, "msgs": [{"INFO": "login: Bad or expired credentials", "SOURCE": "BLL", "ERR_CD": "INVALID_DATA", "LVL": "ERROR"}, {"INFO": "login: There was a problem with your credentials", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}", @headers={"Server"=>"nginx/0.7.67", "Date"=>"Thu, 08 Sep 2011 20:04:21 GMT", "Content-Type"=>"application/json", "Transfer-Encoding"=>"chunked", "Connection"=>"keep-alive"}, @status=400>') }
@dns.instance_variable_get(:@connection).should_receive(:request).exactly(2).times
@dns.auth_token
end
tests("#auth_token with inactivity logout").raises(Excon::Errors::BadRequest) do
pending if Fog.mocking?
@dns = Fog::DNS[:dynect]
@dns.instance_variable_get(:@connection).stub(:request) { raise Excon::Errors::BadRequest.new('Expected(200) <=> Actual(400 Bad Request) request => {:headers=>{"Content-Type"=>"application/json", "API-Version"=>"2.3.1", "Auth-Token"=>"auth-token", "Host"=>"api2.dynect.net:443", "Content-Length"=>0}, :host=>"api2.dynect.net", :mock=>nil, :path=>"/REST/CNAMERecord/domain.com/www.domain.com", :port=>"443", :query=>nil, :scheme=>"https", :expects=>200, :method=>:get} response => #<Excon::Response:0x00000008478b98 @body="{"status": "failure", "data": {}, "job_id": 21326025, "msgs": [{"INFO": "login: inactivity logout", "SOURCE": "BLL", "ERR_CD": "INVALID_DATA", "LVL": "ERROR"}, {"INFO": "login: There was a problem with your credentials", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}", @headers={"Server"=>"nginx/0.7.67", "Date"=>"Thu, 08 Sep 2011 20:04:21 GMT", "Content-Type"=>"application/json", "Transfer-Encoding"=>"chunked", "Connection"=>"keep-alive"}, @status=400>') }
@dns.instance_variable_get(:@connection).should_receive(:request).exactly(2).times
@dns.auth_token
end
end
end