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.
- data/MIT-LICENSE +20 -0
- data/README.md +229 -0
- data/lib/gandi.rb +44 -0
- data/lib/gandi/contact.rb +200 -0
- data/lib/gandi/domain.rb +157 -0
- data/lib/gandi/operation.rb +27 -0
- data/lib/gandi/price.rb +48 -0
- data/lib/gandi/version.rb +3 -0
- data/lib/gandi/zlib_parser_decorator.rb +15 -0
- metadata +55 -0
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/lib/gandi.rb
ADDED
@@ -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
|
data/lib/gandi/domain.rb
ADDED
@@ -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
|
data/lib/gandi/price.rb
ADDED
@@ -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,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:
|