dynectastic 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +24 -0
- data/CHANGES.rdoc +9 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +14 -0
- data/LICENSE +20 -0
- data/README.rdoc +120 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/dynectastic.gemspec +85 -0
- data/lib/dynectastic.rb +26 -0
- data/lib/dynectastic/error_translator.rb +22 -0
- data/lib/dynectastic/errors.rb +33 -0
- data/lib/dynectastic/factories/node_factory.rb +15 -0
- data/lib/dynectastic/factories/record_factory.rb +51 -0
- data/lib/dynectastic/factories/zone_factory.rb +58 -0
- data/lib/dynectastic/job.rb +23 -0
- data/lib/dynectastic/record.rb +32 -0
- data/lib/dynectastic/request.rb +114 -0
- data/lib/dynectastic/resource.rb +44 -0
- data/lib/dynectastic/session.rb +37 -0
- data/lib/dynectastic/zone.rb +56 -0
- data/test/helper.rb +14 -0
- data/test/test_node_factory.rb +35 -0
- data/test/test_record.rb +57 -0
- data/test/test_record_factory.rb +68 -0
- data/test/test_request.rb +64 -0
- data/test/test_resource.rb +26 -0
- data/test/test_session.rb +21 -0
- data/test/test_zone.rb +73 -0
- data/test/test_zone_factory.rb +41 -0
- metadata +135 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class RecordFactory < Resource
|
4
|
+
|
5
|
+
def self.record_types(*attrs)
|
6
|
+
attrs.each do |attr_name|
|
7
|
+
class_eval do
|
8
|
+
attr_reader :record_type
|
9
|
+
end
|
10
|
+
eval %Q[
|
11
|
+
def #{ attr_name }
|
12
|
+
@record_type = "#{ attr_name }".upcase
|
13
|
+
self
|
14
|
+
end
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
record_types :aaaa, :any, :a, :cname, :dnskey, :ds, :key, :loc, :mx, :ns, :ptr, :rp, :soa, :srv, :txt
|
20
|
+
|
21
|
+
def build(attributes)
|
22
|
+
record = Dynectastic::Record.new(session, self)
|
23
|
+
record.type = record_type
|
24
|
+
record.attributes = attributes
|
25
|
+
record
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_id(id, options={})
|
29
|
+
build get("#{ entity_base }/#{ options[:zone] }/#{ options[:node] }/#{ id }")
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_all(options={})
|
33
|
+
records = []
|
34
|
+
get("#{ entity_base }/#{ options[:zone] }/#{ options[:node] }/").each do |record_url|
|
35
|
+
record_id = record_url.split("/").last
|
36
|
+
records << find_by_id(record_id, options)
|
37
|
+
end
|
38
|
+
records
|
39
|
+
end
|
40
|
+
|
41
|
+
def entity_base
|
42
|
+
"/REST/#{ entity_name }"
|
43
|
+
end
|
44
|
+
|
45
|
+
def entity_name
|
46
|
+
"#{ record_type }Record"
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class ZoneFactory < Resource
|
4
|
+
|
5
|
+
def build(attributes)
|
6
|
+
zone = Dynectastic::Zone.new(session, self)
|
7
|
+
zone.attributes = attributes
|
8
|
+
zone
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_by_name(name=nil)
|
12
|
+
build api_parameters_to_attributes(get("#{ entity_base }/#{ name }/"))
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_all
|
16
|
+
zones = []
|
17
|
+
get("#{ entity_base }/").each do |full_zone_path|
|
18
|
+
zone_name = full_zone_path.split('/').last
|
19
|
+
zones << find_by_name(zone_name)
|
20
|
+
end
|
21
|
+
zones
|
22
|
+
end
|
23
|
+
|
24
|
+
def publish(name)
|
25
|
+
put("#{ entity_base }/#{ name }", :body => { :publish => true } )
|
26
|
+
end
|
27
|
+
|
28
|
+
def freeze(name)
|
29
|
+
put("#{ entity_base }/#{ name }", :body => { :freeze => true } )
|
30
|
+
end
|
31
|
+
|
32
|
+
def unfreeze(name)
|
33
|
+
put("#{ entity_base }/#{ name }", :body => { :thaw => true } )
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy(name)
|
37
|
+
delete("#{ entity_base }/#{ name }")
|
38
|
+
end
|
39
|
+
|
40
|
+
def entity_base
|
41
|
+
"/REST/Zone"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def api_parameters_to_attributes(api_params)
|
47
|
+
{
|
48
|
+
:name => api_params['zone'],
|
49
|
+
:contact => api_params['rname'],
|
50
|
+
:ttl => api_params['ttl'],
|
51
|
+
:serial_style => api_params['serial_style'],
|
52
|
+
:type => api_params['zone_type']
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class Job < Resource
|
4
|
+
|
5
|
+
attr_reader :request, :id, :incomplete
|
6
|
+
|
7
|
+
def initialize(request)
|
8
|
+
@request = request
|
9
|
+
@id = request.job_id
|
10
|
+
@incomplete = request.job_incomplete
|
11
|
+
end
|
12
|
+
|
13
|
+
def complete?
|
14
|
+
not incomplete?
|
15
|
+
end
|
16
|
+
|
17
|
+
def incomplete?
|
18
|
+
!! @incomplete
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class Record < Resource
|
4
|
+
|
5
|
+
attr_accessor :zone, :node, :rdata, :ttl, :type, :record_id
|
6
|
+
|
7
|
+
def save
|
8
|
+
payload = {
|
9
|
+
'rdata' => rdata,
|
10
|
+
'ttl' => ttl
|
11
|
+
}
|
12
|
+
|
13
|
+
self.attributes = record_id ? update(payload) : create(payload)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def update(payload)
|
19
|
+
put("#{ entity_path }/#{ record_id }/", :body => payload)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(payload)
|
23
|
+
post("#{ entity_path }/", :body => payload)
|
24
|
+
end
|
25
|
+
|
26
|
+
def entity_path
|
27
|
+
"#{ factory.entity_base }/#{ zone }/#{ node }"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class Request
|
4
|
+
|
5
|
+
include HTTParty
|
6
|
+
|
7
|
+
base_uri API_URL
|
8
|
+
headers "Content-Type" => "application/json"
|
9
|
+
#debug_output $stdout
|
10
|
+
|
11
|
+
attr_reader :resource, :attempts, :response, :job_id, :job_incomplete
|
12
|
+
|
13
|
+
def initialize(resource)
|
14
|
+
@resource = resource
|
15
|
+
end
|
16
|
+
|
17
|
+
def job
|
18
|
+
if job_id
|
19
|
+
Job.new(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform(*args)
|
24
|
+
method, path, options = args.shift, args.shift, args.last
|
25
|
+
options ||= {}
|
26
|
+
prepare_options(options)
|
27
|
+
reset_attempts
|
28
|
+
|
29
|
+
begin
|
30
|
+
process_response(self.class.send(method, path, options))
|
31
|
+
rescue HTTParty::RedirectionTooDeep => e
|
32
|
+
if e.response.body.include?("/REST/Job")
|
33
|
+
memorize_response_data(e.response)
|
34
|
+
false
|
35
|
+
else
|
36
|
+
raise e
|
37
|
+
end
|
38
|
+
|
39
|
+
rescue SessionBusy => e
|
40
|
+
if attempts_left?
|
41
|
+
sleep(10)
|
42
|
+
increment_attempts
|
43
|
+
retry
|
44
|
+
else
|
45
|
+
raise e
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def default_headers
|
53
|
+
self.class.default_options[:headers]
|
54
|
+
end
|
55
|
+
|
56
|
+
def prepare_options(options)
|
57
|
+
if resource.session
|
58
|
+
options = options.merge!(:headers => default_headers.merge({ "Auth-Token" => resource.session.token}))
|
59
|
+
end
|
60
|
+
|
61
|
+
if options[:body].kind_of?(Hash)
|
62
|
+
options[:body] = options[:body].to_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_response(response)
|
67
|
+
memorize_response_data(response)
|
68
|
+
if response['status'] == 'success'
|
69
|
+
response['data']
|
70
|
+
else
|
71
|
+
convert_dynect_messages_to_exceptions(response['msgs'])
|
72
|
+
response
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def convert_dynect_messages_to_exceptions(messages)
|
77
|
+
messages.each do |msg|
|
78
|
+
if msg['LVL'] == 'ERROR'
|
79
|
+
raise ErrorTranslator.translate_to_exception(msg)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def memorize_response_data(response)
|
85
|
+
@response = response
|
86
|
+
|
87
|
+
if response.kind_of?(Net::HTTPTemporaryRedirect)
|
88
|
+
@job_id = extract_job_id_from_response(response)
|
89
|
+
@job_incomplete = true
|
90
|
+
else
|
91
|
+
@job_id = response['job_id']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_job_id_from_response(response)
|
96
|
+
response.body.scan(/\/REST\/Job\/(\d+)/i).flatten.first.to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
def increment_attempts
|
100
|
+
@attempts ||= 0
|
101
|
+
@attempts += 1
|
102
|
+
end
|
103
|
+
|
104
|
+
def reset_attempts
|
105
|
+
@attempts = 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def attempts_left?
|
109
|
+
@attempts < RETRIES
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class Resource
|
4
|
+
|
5
|
+
attr_reader :session, :factory
|
6
|
+
attr_accessor :last_request
|
7
|
+
|
8
|
+
def initialize(session, factory=nil)
|
9
|
+
@session = session
|
10
|
+
@factory = factory
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(*args)
|
14
|
+
request(:get, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post(*args)
|
18
|
+
request(:post, *args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def put(*args)
|
22
|
+
request(:put, *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(*args)
|
26
|
+
request(:delete, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def attributes=(hash)
|
30
|
+
hash.each_pair do |name, value|
|
31
|
+
instance_variable_set("@#{ name }", value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def request(*args)
|
38
|
+
@last_request = Request.new(self)
|
39
|
+
@last_request.perform(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class Session < Resource
|
4
|
+
|
5
|
+
attr_reader :token, :api_version
|
6
|
+
|
7
|
+
def initialize(customer_name, user_name, password)
|
8
|
+
payload = {
|
9
|
+
:customer_name => customer_name,
|
10
|
+
:user_name => user_name,
|
11
|
+
:password => password
|
12
|
+
}.to_json
|
13
|
+
|
14
|
+
response = post(entity_base, :body => payload)
|
15
|
+
@token = response['token']
|
16
|
+
@api_version = response['version']
|
17
|
+
end
|
18
|
+
|
19
|
+
def zones
|
20
|
+
Dynectastic::ZoneFactory.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def nodes
|
24
|
+
Dynectastic::NodeFactory.new(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def records
|
28
|
+
Dynectastic::RecordFactory.new(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def entity_base
|
32
|
+
"/REST/Session"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Dynectastic
|
2
|
+
|
3
|
+
class Zone < Resource
|
4
|
+
|
5
|
+
attr_accessor :name, :contact, :ttl, :serial, :serial_style
|
6
|
+
attr_reader :zone_type
|
7
|
+
|
8
|
+
def save
|
9
|
+
payload = {
|
10
|
+
'rname' => contact,
|
11
|
+
'serial_style' => serial,
|
12
|
+
'ttl' => ttl
|
13
|
+
}
|
14
|
+
self.attributes = post("#{ factory.entity_base }/#{ name }", :body => payload)
|
15
|
+
end
|
16
|
+
|
17
|
+
def publish
|
18
|
+
if factory.publish(name)
|
19
|
+
@published = true
|
20
|
+
else
|
21
|
+
job
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def freeze
|
26
|
+
if factory.freeze(name)
|
27
|
+
@frozen = true
|
28
|
+
else
|
29
|
+
job
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def unfreeze
|
34
|
+
if factory.unfreeze(name)
|
35
|
+
@frozen = false
|
36
|
+
true
|
37
|
+
else
|
38
|
+
job
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy
|
43
|
+
factory.destroy(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def published?
|
47
|
+
@published
|
48
|
+
end
|
49
|
+
|
50
|
+
def frozen?
|
51
|
+
@frozen
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
require 'dynectastic'
|
8
|
+
|
9
|
+
DYNECT_CUST_NAME = "xxx"
|
10
|
+
DYNECT_USER_NAME = "xxx"
|
11
|
+
DYNECT_USER_PASS = "xxx"
|
12
|
+
|
13
|
+
class Test::Unit::TestCase
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestNodeFactory < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "NodeFactory" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@dynect = Dynectastic.session(DYNECT_CUST_NAME, DYNECT_USER_NAME, DYNECT_USER_PASS)
|
9
|
+
@zone = @dynect.zones.build(
|
10
|
+
:name => "dynectastictests.com",
|
11
|
+
:contact => "ilya@wildbit.com",
|
12
|
+
:ttl => 1800
|
13
|
+
)
|
14
|
+
@zone.save
|
15
|
+
|
16
|
+
@record = @dynect.records.cname.build(
|
17
|
+
:zone => "dynectastictests.com",
|
18
|
+
:node => "ilya.dynectastictests.com",
|
19
|
+
:ttl => 0,
|
20
|
+
:rdata => { :cname => "something.dynectastictests.com" }
|
21
|
+
)
|
22
|
+
@record.save
|
23
|
+
end
|
24
|
+
|
25
|
+
teardown do
|
26
|
+
@zone.destroy
|
27
|
+
end
|
28
|
+
|
29
|
+
should "delete" do
|
30
|
+
@dynect.nodes.destroy("dynectastictests.com", "ilya.dynectastictests.com")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|