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.
@@ -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