dynectastic 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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