viaduct-gandi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Viaduct Hosting Limited.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,229 @@
1
+ # Gandi API Library
2
+
3
+ This is a short library to facilitate working with the Gandi API for
4
+ domain registrations. While there are other libraries for this purpose
5
+ this offers a more satisfying syntax.
6
+
7
+ ## Installation
8
+
9
+ As usual, just pop a reference to the gem into your `Gemfile` and run
10
+ `bundle`. Alternatively, just install the gem and require it manually.
11
+
12
+ ```ruby
13
+ gem 'viaduct-gandi', :require => 'gandi'
14
+ ```
15
+
16
+ ## Configuration
17
+
18
+ Before you can run commands, you'll need to configure the library.
19
+
20
+ ```ruby
21
+ Gandi.apikey = 'abc123abc123abc123abc'
22
+ Gandi.mode = 'test' # or 'live'
23
+ ```
24
+
25
+ ## Pricing
26
+
27
+ You can obtain pricing for registrations very easily. This example is the
28
+ most basic form of obtaining a price. In this instance, it returns the price
29
+ for registering a .com domain.
30
+
31
+ ```ruby
32
+ price = Gandi::Price.find('example.com')
33
+ price.action #=> 'create'
34
+ price.description #=> '.com'
35
+ price.price #=> 5.5
36
+ price.min_duration #=> 1
37
+ price.max_duration #=> 10
38
+ price.currency #=> 'GBP'
39
+ price.grid #=> 'E'
40
+ price.duration_unit #=> 'y'
41
+ ```
42
+
43
+ There are a number of options which can be passed to method.
44
+
45
+ * `:action` - which service you will be using (create, renew, restore, transfer, plus others - create is default)
46
+ * `:currency` - which currency to return a price in (GBP, EUR or USD - GBP is default)
47
+ * `:grid` - your pricing grid (A,B,C,D,E - E is default)
48
+ * `:duration` - the duration of the registration (a positive interger - 1 is default)
49
+
50
+ ## Contacts
51
+
52
+ At the root of all domain registrations is a contact. Each contact has a unique `handle`
53
+ which identifies the contact and is needed for all registrations.
54
+
55
+ ### Creating a new contact
56
+
57
+ ```ruby
58
+ contact = Gandi::Contact.new
59
+ contact.type = 0
60
+ contact.first_name = 'Adam'
61
+ contact.last_name = 'Cooke'
62
+ contact.address = 'Unit 9 Winchester Place'
63
+ contact.city = 'Poole'
64
+ contact.country = 'GB'
65
+ contact.email_address = 'adam@example.com'
66
+ contact.phone = '+44.1202901101'
67
+ contact.password = 'randompasswordhere'
68
+ contact.save #=> true
69
+ contact.handle #=> 'AC1234-GANDI'
70
+ ```
71
+
72
+ If an error occurs here there are two possible exceptions which may be raised (as the
73
+ same applies to updates). If a validation error is caught locally you will find a
74
+ `Gandi::ValidationError` is raised otherwise you'll receive `Gandi::DataError` with some
75
+ mostly uninteligiable garbage from the Gandi API.
76
+
77
+ ### Finding a contact
78
+
79
+ ```ruby
80
+ contact = Gandi::Contact.find('AC1234')
81
+ contact.first_name #=> 'Adam'
82
+ ```
83
+
84
+ ### Updating a contact
85
+
86
+ To update a contact you need to find it and then make changes to its attributes followed
87
+ by a save operation.
88
+
89
+ ```ruby
90
+ contact = Gandi::Contact.find('AC1234')
91
+ contact.phone = '+44.123451234'
92
+ contact.save
93
+ ```
94
+
95
+ ### Determine if a contact can be associated with a domain
96
+
97
+ If you wish to see whether a contact is suitable for registration with a given domain,
98
+ you can call this method on any contact.
99
+
100
+ ```ruby
101
+ contact.can_associate?('yourdomain.com')
102
+ ```
103
+
104
+ This will return true or an error string straight from the Gandi API.
105
+
106
+ ### Return associated domains
107
+
108
+ If you wish to return all domains associated with a contact, you can call the `domains`
109
+ method.
110
+
111
+ ```ruby
112
+ contact.domains #=> [Gandi::Domain, Gandi::Domains, ...]
113
+ ```
114
+
115
+ ## Domains
116
+
117
+ Once you have some contacts, you can easily manage domains through through the library.
118
+
119
+ ### TLDs
120
+
121
+ You can get a full list of TLDs which can be registered using the Gandi API using the method
122
+ shown below. This will return an array of hashes with TLD information.
123
+
124
+ ```ruby
125
+ Gandi::Domains.tlds
126
+ ```
127
+
128
+ ### Checking domain availability
129
+
130
+ Gandi provides a asyncronous method for checking domain availability. This library provides
131
+ methods for determining availability both asyncronously and syncronously.
132
+
133
+ The asyncronous method accepts a number of domains to check and will return a hash
134
+ of domains with their availability status. Any status which is 'pending' has not been determined
135
+ yet and another call should be made in the near future to check again.
136
+
137
+ ```ruby
138
+ Gandi::Domain.check_availability('domain1.com', 'domain2.io') #=> {'domain1.com' => 'pending', 'domain2.io' => 'pending'}
139
+ ```
140
+
141
+ The syncronous method will keep calling the API until it gets a non-pending status for all
142
+ requested domains. Pending will only be returned if the status cannot be determined within
143
+ 20 requests to the Gandi API.
144
+
145
+ ```ruby
146
+ Gandi::Domain.check_availability!('domain1.com', 'domain2.io') #=> {'domain1.com' => 'unavailable', 'domain2.io' => 'available'}
147
+ ```
148
+
149
+ In addition to these methods, there is also a shorthand syncronous method for checking a
150
+ single domain's status.
151
+
152
+ ```ruby
153
+ Gandi::Domain.available?('blahblahblah.com') #=> true or false
154
+ ```
155
+
156
+ ### Create/register a new domain
157
+
158
+ In order to register a domain you need to create a new domain registration object. Once
159
+ created the domain will be set up automatically. You should monitor the operation's 'step'
160
+ attribute to see how things are progressing. When this is 'DONE', the domain has been
161
+ registered.
162
+
163
+ ```ruby
164
+ if Gandi::Domain.available?('somedomain.com')
165
+ # Create a domain registration request
166
+ operation = Gandi::Domain.create('blahblahblah.com', :owner => 'ABC123')
167
+ # Monitor the operation status
168
+ operation.step #=> 'BILL'
169
+ operation.reload
170
+ operation.step #=> 'RUN'
171
+ operation.reload
172
+ operation.step #=> 'DONE' or 'ERROR'
173
+ operation.last_error #=> 'An error message here if failed'
174
+ else
175
+ # Domain is not available for registration
176
+ end
177
+ ```
178
+
179
+ ### Looking up domain information
180
+
181
+ Once a domain has been registered, you can look it up using the following method.
182
+
183
+ ```ruby
184
+ domain = Gandi::Domain.find('blahblahblah.com')
185
+ domain.name #=> "blahblahblah.com"
186
+ domain.owner #=> A Gandi::Contact instance for the owner\
187
+ domain.partial? #=> false
188
+ ```
189
+
190
+ In addition to these methods, there are a number of other attributes which are available.
191
+ Consult with the [Gandi docs](http://doc.rpc.gandi.net/domain/reference.html#DomainReturn)
192
+ for full details.
193
+
194
+ ### Changing contacts
195
+
196
+ To change the contact associated with a domain you can use the method below. You must
197
+ pass the Gandi handle for the new contact.
198
+
199
+ ```ruby
200
+ domain = Gandi::Domain.find('blahblahblah.com')
201
+ operation = domain.change_contacts('admin' => 'DEF123', 'bill' => 'DEF123', 'tech' => 'DEF123')
202
+ ```
203
+
204
+ Note: you cannot change the owner with this method and you don't need to change all
205
+ the contact types. You should monitor the operation object to determine the status.
206
+
207
+ ### Renewing a domain
208
+
209
+ If you wish to renew a domain, you can use the method below.
210
+
211
+ ```ruby
212
+ domain = Gandi::Domain.find('blahblahblah.com')
213
+ operation = domain.renew(2015, 2)
214
+ ```
215
+
216
+ The first argument here is the current expiry year and the second argument is the duration
217
+ for the renewal. You should monitor the operation object to determine the status.
218
+
219
+ ### Restoring a domain
220
+
221
+ If you wish to restore a domain, you can use the method below.
222
+
223
+ ```ruby
224
+ domain = Gandi::Domain.find('blahblahblah.com')
225
+ operation = domain.restore(2)
226
+ ```
227
+
228
+ The first argument here is the duration you wish to renew the domain for.
229
+ You should monitor the operation object to determine the status.
@@ -0,0 +1,44 @@
1
+ require 'xmlrpc/client'
2
+ require 'gandi/zlib_parser_decorator'
3
+ require 'gandi/version'
4
+ require 'gandi/price'
5
+ require 'gandi/contact'
6
+ require 'gandi/domain'
7
+ require 'gandi/operation'
8
+
9
+ module Gandi
10
+
11
+ class Error < StandardError; end
12
+ class DataError < Error; end
13
+ class ServerError < Error; end
14
+ class ValidationError < Error; end
15
+
16
+
17
+ class << self
18
+ attr_accessor :apikey
19
+ attr_accessor :mode
20
+
21
+ def endpoint
22
+ mode == 'live' ? 'https://rpc.gandi.net/xmlrpc/' : 'https://rpc.ote.gandi.net/xmlrpc/'
23
+ end
24
+
25
+ def client
26
+ @client ||= begin
27
+ XMLRPC::Config.module_eval do
28
+ remove_const(:ENABLE_NIL_PARSER)
29
+ const_set(:ENABLE_NIL_PARSER, true)
30
+ end
31
+ client = XMLRPC::Client.new2(self.endpoint)
32
+ client.http_header_extra = {"Accept-Encoding" => "gzip"}
33
+ client.set_parser ZlibParserDecorator.new(client.send(:parser))
34
+ client
35
+ end
36
+ end
37
+
38
+ def call(name, *args)
39
+ client.call(name, apikey, *args)
40
+ rescue XMLRPC::FaultException => e
41
+ raise(e.faultCode < 500000 ? ServerError : DataError, e.faultString)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,200 @@
1
+ module Gandi
2
+ class Contact
3
+
4
+ COUNTRIES = ['AF','ZA','AL','DZ','DE','AD','AO','AI','AG','AN','SA','AR','AM','AW','AC','AU','AT','AZ','BS','BH','BD','BB','BY','BE','BZ','BJ','BM','BT','BO','BA','BW','BR','BN','BG','BF','BI','KH','CM','CA','CV','KY','CF','CL','CN','CY','CO','KM','CG','CD','CK','KR','KP','CR','CI','HR','CU','DK','DJ','DO','DM','EG','SV','AE','EC','ER','ES','EE','US','ET','FK','FO','FJ','FI','FR','GA','GM','GE','GH','GI','GR','GD','GL','GU','GT','GN','GQ','GW','GY','HT','HI','HN','HK','HU','IN','ID','IR','IQ','IE','IS','IL','IT','JM','JP','JE','JO','KZ','KE','KG','KI','KW','LA','LS','LV','LB','LR','LY','LI','LT','LU','MO','MK','MG','MY','MW','MV','ML','MT','MP','MA','MH','MU','MR','MX','FM','MD','MC','MN','MS','MZ','MM','NA','NR','NP','NI','NE','NG','NU','NF','NO','NZ','OM','UG','UZ','PK','PW','PS','PA','PG','PY','NL','PE','PH','PL','PT','PR','QA','RO','GB','RU','RW','SH','LC','KN','SM','VC','SB','AS','WS','ST','SN','SC','SL','SG','SK','SI','SO','SD','LK','SE','CH','SR','SZ','SY','TJ','TW','TZ','TD','CZ','TH','TP','TG','TK','TO','TT','TN','TM','TC','TR','TV','UA','UY','VU','VE','VI','VG','VN','YE','ZM','ZW','RE','PF','AQ','GF','NC','MQ','GP','PM','TF','YT','RS','TL','CC','CX','HM','AX','BV','GS','GG','IM','ME','IO','PN','EH','BL','MF','VA','SJ','WF','EP']
5
+ TYPES = {0 => 'person', 1 => 'company', 2 => 'association', 3 => 'public_body'}
6
+ ATTRIBUTE_MAP = {
7
+ 'type' => 'type',
8
+ 'given' => 'first_name',
9
+ 'family' => 'last_name',
10
+ 'orgname' => 'organization',
11
+ 'password' => 'password',
12
+ 'streetaddr' => 'address',
13
+ 'city' => 'city',
14
+ 'zip' => 'post_code',
15
+ 'country' => 'country',
16
+ 'email' => 'email_address',
17
+ 'phone' => 'phone',
18
+ 'extra_parameters' => 'extra_parameters'
19
+ }
20
+
21
+ # Type of object
22
+ attr_reader :handle
23
+ attr_reader :attributes
24
+ attr_accessor :type
25
+
26
+ # Name
27
+ attr_accessor :first_name
28
+ attr_accessor :last_name
29
+ attr_accessor :organization
30
+
31
+ # Credentials
32
+ attr_accessor :password
33
+
34
+ # Address Fields
35
+ attr_accessor :address
36
+ attr_accessor :city
37
+ attr_accessor :post_code
38
+ attr_accessor :country
39
+
40
+ # Contact details
41
+ attr_accessor :email_address
42
+ attr_accessor :phone
43
+ attr_accessor :extra_parameters
44
+
45
+ def initialize
46
+ @attributes = {}
47
+ end
48
+
49
+ #
50
+ # Find a contact based on it's handle
51
+ #
52
+ def self.find(handle)
53
+ info = Gandi.call("contact.info", handle)
54
+ contact = self.new
55
+ contact.set_attributes(info)
56
+ contact
57
+ rescue Gandi::DataError => e
58
+ e.message =~ /CAUSE_NOTFOUND/ ? nil : raise
59
+ end
60
+
61
+ #
62
+ # Return extra parameters for this contact
63
+ #
64
+ def extra_parameters
65
+ @extra_parameters ||= {}
66
+ end
67
+
68
+ #
69
+ # Save the contact's information
70
+ #
71
+ def save
72
+ new_record? ? create : update
73
+ end
74
+
75
+ #
76
+ # Is this a new record?
77
+ #
78
+ def new_record?
79
+ self.handle.nil?
80
+ end
81
+
82
+ #
83
+ # Create a contact
84
+ #
85
+ def create
86
+ return false unless new_record?
87
+ raise Gandi::ValidationError, self.errors unless self.errors.empty?
88
+ result = Gandi.call('contact.create', dirty_attributes_with_remote_keys)
89
+ set_attributes(result)
90
+ true
91
+ end
92
+
93
+ #
94
+ # Update a contact
95
+ #
96
+ def update
97
+ return false if new_record?
98
+ raise Gandi::ValidationError, self.errors unless self.errors.empty?
99
+ result = Gandi.call('contact.update', self.handle, dirty_attributes_with_remote_keys)
100
+ set_attributes(result)
101
+ true
102
+ end
103
+
104
+ #
105
+ # Is the current instance dirty? (I.e. need updating remotely)
106
+ #
107
+ def dirty?
108
+ dirty_attributes.empty?
109
+ end
110
+
111
+ #
112
+ # Returns a hash of all dirty attributes (with local keys)
113
+ #
114
+ def dirty_attributes
115
+ ATTRIBUTE_MAP.values.inject(Hash.new) do |hash, local|
116
+ if @attributes.keys.include?(local) && @attributes[local] == self.send(local)
117
+ next hash
118
+ else
119
+ hash[local] = self.send(local)
120
+ end
121
+ hash
122
+ end
123
+ end
124
+
125
+ #
126
+ # Returns a hash of all dirty_attributes (with remote keys)
127
+ #
128
+ def dirty_attributes_with_remote_keys
129
+ dirty_attributes.inject(Hash.new) do |hash, (local, value)|
130
+ remote = ATTRIBUTE_MAP.select { |k,v| v == local }.first[0]
131
+ hash[remote] = value || ''
132
+ hash
133
+ end
134
+ end
135
+
136
+ #
137
+ # Set the attributes from a remote hash
138
+ #
139
+ def set_attributes(hash)
140
+ @handle = hash['handle']
141
+ @attributes = {}
142
+ ATTRIBUTE_MAP.each do |remote, local|
143
+ self.send(local + '=', hash[remote])
144
+ remote_value = hash[remote]
145
+ remote_value = remote_value.dup if remote_value && !remote_value.is_a?(Fixnum)
146
+ @attributes[local] = remote_value
147
+ end
148
+ end
149
+
150
+ #
151
+ # Validate the contact looks OK to avoid getting ugly-ass and unhelpful messages
152
+ # from the Gandi endpoint.
153
+ #
154
+ # Returns an array of problems or empty if no issues.
155
+ #
156
+ def errors
157
+ Hash.new.tap do |a|
158
+ a[:type] = "must be one of #{TYPES.keys.join(',')}" unless TYPES.keys.include?(self.type)
159
+ a[:city] = 'is required' if is_blank?(self.city)
160
+ a[:first_name] = 'is required' if is_blank?(self.first_name)
161
+ a[:last_name] = 'is required' if is_blank?(self.last_name)
162
+ a[:organization] = 'is required' if is_blank?(self.organization) && [1,2,3].include?(self.type)
163
+ a[:country] = 'is invalid' unless COUNTRIES.include?(self.country)
164
+ a[:address] = 'is required' if is_blank?(self.address)
165
+ a[:phone] = 'is invalid' unless self.phone =~ /^\+\d{1,3}\.\d+$/
166
+ a[:password] = 'is required' if is_blank?(self.password) && new_record?
167
+ a[:email_address] = 'is invalid' unless self.email_address =~ /^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[-a-z0-9]{2,63})$/
168
+ end
169
+ end
170
+
171
+ #
172
+ # Is the given variable blank?
173
+ #
174
+ def is_blank?(variable)
175
+ variable.nil? || variable.length == 0
176
+ end
177
+
178
+ #
179
+ # Return whether this contact is suitable for use with a certain domain
180
+ #
181
+ def can_associate?(domain)
182
+ spec = {'domain' => domain, 'owner' => true, 'admin' => true}
183
+ res = Gandi.call('contact.can_associate_domain', self.handle, spec)
184
+ if res == true
185
+ true
186
+ else
187
+ res
188
+ end
189
+ end
190
+
191
+ #
192
+ # Return an array of domains associated with this contact
193
+ #
194
+ def domains
195
+ return [] if new_record?
196
+ Gandi.call('domain.list', {'handle' => self.handle}).map { |d| Domain.new(d, true)}
197
+ end
198
+
199
+ end
200
+ end
@@ -0,0 +1,157 @@
1
+ module Gandi
2
+ class Domain
3
+
4
+ class << self
5
+
6
+ #
7
+ # Return all available TLDs
8
+ #
9
+ def tlds
10
+ Gandi.call('domain.tld.list')
11
+ end
12
+
13
+ #
14
+ # Check the availability of a set of domains
15
+ #
16
+ def check_availability(*domains)
17
+ Gandi.call('domain.available', domains)
18
+ end
19
+
20
+ #
21
+ # Check the availability of a set of domains syncronously
22
+ #
23
+ def check_availability!(*domains)
24
+ max_checks = 10
25
+ loop do
26
+ result = check_availability(*domains)
27
+ return result if result.all? { |k,v| v != 'pending' }
28
+ max_checks -= 1
29
+ return result if max_checks <= 0
30
+ sleep 0.7
31
+ end
32
+ end
33
+
34
+ #
35
+ # Check the availability of a given domain
36
+ #
37
+ def available?(domain, max_checks = 10)
38
+ loop do
39
+ result = check_availability(domain)
40
+ return result[domain] == 'available' unless result[domain] == 'pending'
41
+ max_checks -= 1
42
+ return false if max_checks <= 0
43
+ sleep 0.7
44
+ end
45
+ end
46
+
47
+ #
48
+ # Return the total number of domains on the account
49
+ #
50
+ def count
51
+ Gandi.call('domain.count')
52
+ end
53
+
54
+ #
55
+ # Register a domain for the given contact. This is a billable action
56
+ # so be cautious. This method will return an operation object as this
57
+ # is not run straight away.
58
+ #
59
+ def create(domain, options = {})
60
+ raise ValidationError, "You must specify 'owner' as an option" unless options[:owner]
61
+ spec = {}
62
+ spec['duration'] = options[:duration] || 1
63
+ spec['owner'] = options[:owner]
64
+ spec['admin'] = options[:admin] || options[:owner]
65
+ spec['bill'] = options[:bill] || options[:owner]
66
+ spec['tech'] = options[:tech] || options[:owner]
67
+ spec['nameservers'] = options[:nameservers] if options[:nameservers]
68
+ spec['zone_id'] = options[:zone_id] if options[:zone_id]
69
+ result = Gandi.call('domain.create', domain, spec)
70
+ Operation.new(result)
71
+ end
72
+
73
+ #
74
+ # Return information about a doman
75
+ #
76
+ def find(name)
77
+ self.new(Gandi.call('domain.info', name))
78
+ end
79
+
80
+ #
81
+ # Return an array of all domains
82
+ #
83
+ def list
84
+ Gandi.call('domain.list').map { |d| self.new(d, true) }
85
+ end
86
+
87
+ end
88
+
89
+ def initialize(attributes, partial = false)
90
+ @attributes = attributes
91
+ @partial = partial
92
+ end
93
+
94
+ def partial?
95
+ @partial
96
+ end
97
+
98
+ def to_s
99
+ "#<Gandi::Domain #{name}>"
100
+ end
101
+
102
+ #
103
+ # Return the domain name
104
+ #
105
+ def name
106
+ @attributes['fqdn']
107
+ end
108
+
109
+ #
110
+ # Return attributes
111
+ #
112
+ def method_missing(name, value = nil)
113
+ if @attributes.keys.include?(name.to_s)
114
+ @attributes[name.to_s]
115
+ else
116
+ super
117
+ end
118
+ end
119
+
120
+ #
121
+ # Get all the latest information about the domain
122
+ #
123
+ def reload
124
+ @attributes = Gandi.call('domain.info', name)
125
+ self
126
+ end
127
+
128
+ #
129
+ # Return the domain's owner object
130
+ #
131
+ def owner
132
+ @owner ||= Gandi::Contact.find(contacts['owner']['handle'])
133
+ end
134
+
135
+ #
136
+ # Change the domains. Accepts a hash
137
+ #
138
+ def change_contacts(hash)
139
+ Operation.new(Gandi.call('domain.contacts.set', name, hash))
140
+ end
141
+
142
+ #
143
+ # Renew the domain
144
+ #
145
+ def renew(current_year, duration = 1)
146
+ Operation.new(Gandi.call('domain.renew', name, 'current_year' => current_year, 'duration' => duration))
147
+ end
148
+
149
+ #
150
+ # Restore the domain
151
+ #
152
+ def restore(duration = 1)
153
+ Operation.new(Gandi.call('domain.restore', name, 'duration' => duration))
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,27 @@
1
+ module Gandi
2
+ class Operation
3
+
4
+ def self.find(id)
5
+ self.new(Gandi.call('operation.info', id))
6
+ end
7
+
8
+ def initialize(attributes)
9
+ @attributes = attributes
10
+ end
11
+
12
+
13
+ def method_missing(name, value = nil)
14
+ if @attributes.keys.include?(name.to_s)
15
+ @attributes[name.to_s]
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def reload
22
+ @attributes = Gandi.call('operation.info', @attributes['id'])
23
+ self
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module Gandi
2
+ class Price
3
+
4
+ attr_accessor :action
5
+ attr_accessor :description
6
+ attr_accessor :price
7
+ attr_accessor :min_duration
8
+ attr_accessor :max_duration
9
+ attr_accessor :currency
10
+ attr_accessor :grid
11
+ attr_accessor :duration_unit
12
+
13
+ def self.find(domain, options = {})
14
+ options[:action] ||= 'create'
15
+ options[:duration] ||= 1
16
+ options[:currency] ||= 'GBP'
17
+ options[:grid] ||= 'E'
18
+ product_spec = {
19
+ 'product' => {
20
+ 'description' => domain,
21
+ 'type' => 'domain'
22
+ },
23
+ 'action' => {
24
+ 'name' => options[:action],
25
+ :duration => options[:duration],
26
+ 'param' => {'tld_phase' => 'golive'}
27
+ }
28
+ }
29
+ list = Gandi.client.call("catalog.list", Gandi.apikey, product_spec, options[:currency], options[:grid])
30
+ if list.size == 1
31
+ price = self.new
32
+ price.action = list.first['action']['name']
33
+ price.description = list.first['product']['description']
34
+ return nil unless list.first['unit_price'].first
35
+ price.price = list.first['unit_price'].first['price']
36
+ price.min_duration = list.first['unit_price'].first['min_duration']
37
+ price.max_duration = list.first['unit_price'].first['max_duration']
38
+ price.currency = list.first['unit_price'].first['currency']
39
+ price.grid = list.first['unit_price'].first['grid']
40
+ price.duration_unit = list.first['unit_price'].first['duration_unit']
41
+ price
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Gandi
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,15 @@
1
+ module Gandi
2
+ class ZlibParserDecorator
3
+ def initialize(parser)
4
+ @parser = parser
5
+ end
6
+
7
+ def parseMethodResponse(responseText)
8
+ @parser.parseMethodResponse(Zlib::GzipReader.new(StringIO.new(responseText)).read)
9
+ end
10
+
11
+ def parseMethodCall(*args)
12
+ @parser.parseMethodCall(*args)
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: viaduct-gandi
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adam Cooke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-01-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A Gandi module for working with domain registrations.
15
+ email:
16
+ - adam@viaduct.io
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/gandi/contact.rb
22
+ - lib/gandi/domain.rb
23
+ - lib/gandi/operation.rb
24
+ - lib/gandi/price.rb
25
+ - lib/gandi/version.rb
26
+ - lib/gandi/zlib_parser_decorator.rb
27
+ - lib/gandi.rb
28
+ - MIT-LICENSE
29
+ - README.md
30
+ homepage: http://viaduct.io
31
+ licenses: []
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.23
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: A Gandi module.
54
+ test_files: []
55
+ has_rdoc: