dynectastic 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|