viaduct-gandi 1.0.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,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: