gandi_v5 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/.travis.yml +23 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/Guardfile +40 -0
- data/LICENSE.md +32 -0
- data/README.md +94 -0
- data/Rakefile +3 -0
- data/TODO.md +29 -0
- data/bin/console +13 -0
- data/gandi_v5.gemspec +41 -0
- data/lib/gandi_v5/billing/info/prepaid.rb +33 -0
- data/lib/gandi_v5/billing/info.rb +26 -0
- data/lib/gandi_v5/billing.rb +28 -0
- data/lib/gandi_v5/data/converter/array_of.rb +35 -0
- data/lib/gandi_v5/data/converter/symbol.rb +26 -0
- data/lib/gandi_v5/data/converter/time.rb +28 -0
- data/lib/gandi_v5/data/converter.rb +41 -0
- data/lib/gandi_v5/data.rb +244 -0
- data/lib/gandi_v5/domain/auto_renew.rb +64 -0
- data/lib/gandi_v5/domain/contact.rb +102 -0
- data/lib/gandi_v5/domain/contract.rb +22 -0
- data/lib/gandi_v5/domain/dates.rb +44 -0
- data/lib/gandi_v5/domain/renewal_information.rb +41 -0
- data/lib/gandi_v5/domain/restore_information.rb +18 -0
- data/lib/gandi_v5/domain/sharing_space.rb +21 -0
- data/lib/gandi_v5/domain.rb +431 -0
- data/lib/gandi_v5/email/mailbox/responder.rb +36 -0
- data/lib/gandi_v5/email/mailbox.rb +236 -0
- data/lib/gandi_v5/email/offer.rb +27 -0
- data/lib/gandi_v5/email/slot.rb +134 -0
- data/lib/gandi_v5/email.rb +11 -0
- data/lib/gandi_v5/error/gandi_error.rb +21 -0
- data/lib/gandi_v5/error.rb +9 -0
- data/lib/gandi_v5/live_dns/domain.rb +211 -0
- data/lib/gandi_v5/live_dns/record_set.rb +79 -0
- data/lib/gandi_v5/live_dns/zone/snapshot.rb +62 -0
- data/lib/gandi_v5/live_dns/zone.rb +301 -0
- data/lib/gandi_v5/live_dns.rb +30 -0
- data/lib/gandi_v5/organization.rb +66 -0
- data/lib/gandi_v5/version.rb +5 -0
- data/lib/gandi_v5.rb +178 -0
- data/spec/.rubocop.yml +4 -0
- data/spec/features/domain_spec.rb +45 -0
- data/spec/features/livedns_domain_spec.rb +8 -0
- data/spec/features/livedns_zone_spec.rb +45 -0
- data/spec/features/mailbox_spec.rb +18 -0
- data/spec/fixtures/bodies/GandiV5_Billing/info.yaml +10 -0
- data/spec/fixtures/bodies/GandiV5_Domain/availability.yaml +15 -0
- data/spec/fixtures/bodies/GandiV5_Domain/fetch_contacts.yaml +8 -0
- data/spec/fixtures/bodies/GandiV5_Domain/get.yaml +37 -0
- data/spec/fixtures/bodies/GandiV5_Domain/list.yaml +20 -0
- data/spec/fixtures/bodies/GandiV5_Domain/renewal_info.yaml +12 -0
- data/spec/fixtures/bodies/GandiV5_Domain/restore_info.yaml +5 -0
- data/spec/fixtures/bodies/GandiV5_Domain/tld.yaml +10 -0
- data/spec/fixtures/bodies/GandiV5_Domain/tlds.yaml +7 -0
- data/spec/fixtures/bodies/GandiV5_Email_Mailbox/get.yaml +16 -0
- data/spec/fixtures/bodies/GandiV5_Email_Mailbox/list.yaml +8 -0
- data/spec/fixtures/bodies/GandiV5_Email_Slot/get.yaml +10 -0
- data/spec/fixtures/bodies/GandiV5_Email_Slot/list.yaml +8 -0
- data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/get.yaml +4 -0
- data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/list.yaml +2 -0
- data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/get.yaml +11 -0
- data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/list.yaml +11 -0
- data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone_Snapshot/get.yaml +9 -0
- data/spec/fixtures/bodies/GandiV5_Organization/get.yaml +17 -0
- data/spec/fixtures/vcr/Domain_features/List_domains.yml +54 -0
- data/spec/fixtures/vcr/Domain_features/Renew_domain.yml +133 -0
- data/spec/fixtures/vcr/LiveDNS_Domain_features/List_domains.yml +32 -0
- data/spec/fixtures/vcr/LiveDNS_Zone_features/List_zones.yml +42 -0
- data/spec/fixtures/vcr/LiveDNS_Zone_features/Make_and_save_snapshot.yml +72 -0
- data/spec/fixtures/vcr/LiveDNS_Zone_features/Save_zone_to_file.yml +28 -0
- data/spec/fixtures/vcr/Mailbox_features/List_mailboxes.yml +39 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/test.env +1 -0
- data/spec/units/gandi_v5/billing/info/prepaid_spec.rb +20 -0
- data/spec/units/gandi_v5/billing/info_spec.rb +4 -0
- data/spec/units/gandi_v5/billing_spec.rb +41 -0
- data/spec/units/gandi_v5/data/converter/array_of_spec.rb +18 -0
- data/spec/units/gandi_v5/data/converter/symbol_spec.rb +16 -0
- data/spec/units/gandi_v5/data/converter/time_spec.rb +16 -0
- data/spec/units/gandi_v5/data/converter_spec.rb +31 -0
- data/spec/units/gandi_v5/data_spec.rb +340 -0
- data/spec/units/gandi_v5/domain/auto_renew_spec.rb +70 -0
- data/spec/units/gandi_v5/domain/contact_spec.rb +36 -0
- data/spec/units/gandi_v5/domain/contract_spec.rb +4 -0
- data/spec/units/gandi_v5/domain/dates_spec.rb +4 -0
- data/spec/units/gandi_v5/domain/renewal_information_spec.rb +81 -0
- data/spec/units/gandi_v5/domain/restore_information_spec.rb +4 -0
- data/spec/units/gandi_v5/domain/sharing_space_spec.rb +4 -0
- data/spec/units/gandi_v5/domain_spec.rb +451 -0
- data/spec/units/gandi_v5/email/mailbox/responder_spec.rb +131 -0
- data/spec/units/gandi_v5/email/mailbox_spec.rb +384 -0
- data/spec/units/gandi_v5/email/offer_spec.rb +17 -0
- data/spec/units/gandi_v5/email/slot_spec.rb +102 -0
- data/spec/units/gandi_v5/error/gandi_error_spec.rb +30 -0
- data/spec/units/gandi_v5/error_spec.rb +4 -0
- data/spec/units/gandi_v5/live_dns/domain_spec.rb +247 -0
- data/spec/units/gandi_v5/live_dns/record_set_spec.rb +74 -0
- data/spec/units/gandi_v5/live_dns/zone/snapshot_spec.rb +37 -0
- data/spec/units/gandi_v5/live_dns/zone_spec.rb +329 -0
- data/spec/units/gandi_v5/live_dns_spec.rb +17 -0
- data/spec/units/gandi_v5/organization_spec.rb +30 -0
- data/spec/units/gandi_v5_spec.rb +204 -0
- metadata +406 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'mailbox/responder'
|
4
|
+
|
5
|
+
class GandiV5
|
6
|
+
class Email
|
7
|
+
# A mailbox that lives within a domain.
|
8
|
+
# @!attribute [r] address
|
9
|
+
# @return [String] full email address.
|
10
|
+
# @!attribute [r] fqdn
|
11
|
+
# @return [String] domain name.
|
12
|
+
# @!attribute [r] uuid
|
13
|
+
# @return [String]
|
14
|
+
# @!attribute [r] login
|
15
|
+
# @return [String] mailbox login.
|
16
|
+
# @!attribute [r] type
|
17
|
+
# @return [:standard, :premium, :free]
|
18
|
+
# @!attribute [r] quota_used
|
19
|
+
# @return [Integer]
|
20
|
+
# @!attribute [r] aliases
|
21
|
+
# @return [nil, Array<String>] mailbox alias list.
|
22
|
+
# A local-part (what comes before the "@") of an email address. It can contain a wildcard
|
23
|
+
# "*" before or after at least two characters to redirect everything thats matches the
|
24
|
+
# local-part pattern.
|
25
|
+
# @!attribute [r] fallback_email
|
26
|
+
# @return [nil, String] fallback email addresse.
|
27
|
+
# @!attribute [r] responder
|
28
|
+
# @return [nil, GandiV5::Email::Mailbox::Responder]
|
29
|
+
class Mailbox
|
30
|
+
include GandiV5::Data
|
31
|
+
|
32
|
+
TYPES = %i[standard premium free].freeze
|
33
|
+
QUOTAS = {
|
34
|
+
free: 3 * 1024**3,
|
35
|
+
standard: 3 * 1024**3,
|
36
|
+
premium: 50 * 1024**3
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
members :address, :login, :quota_used, :aliases, :fallback_email
|
40
|
+
member :type, gandi_key: 'mailbox_type', converter: GandiV5::Data::Converter::Symbol
|
41
|
+
member :uuid, gandi_key: 'id'
|
42
|
+
member :fqdn, gandi_key: 'domain'
|
43
|
+
member :responder, converter: GandiV5::Email::Mailbox::Responder
|
44
|
+
|
45
|
+
alias mailbox_uuid uuid
|
46
|
+
|
47
|
+
# Delete the mailbox and it's contents.
|
48
|
+
# If you delete a mailbox for which you have purchased a slot,
|
49
|
+
# this action frees the slot so it once again becomes available
|
50
|
+
# for use with a new mailbox, or for deletion.
|
51
|
+
# @see https://api.gandi.net/docs/email#delete-v5-email-mailboxes-domain-mailbox_id
|
52
|
+
# @return [String] The confirmation message from Gandi.
|
53
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
54
|
+
def delete
|
55
|
+
data = GandiV5.delete url
|
56
|
+
data['message']
|
57
|
+
end
|
58
|
+
|
59
|
+
# Purge the contents of the mailbox.
|
60
|
+
# @see https://api.gandi.net/docs/email#delete-v5-email-mailboxes-domain-mailbox_id-contents
|
61
|
+
# @return [String] The confirmation message from Gandi.
|
62
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
63
|
+
def purge
|
64
|
+
data = GandiV5.delete "#{url}/contents"
|
65
|
+
data['message']
|
66
|
+
end
|
67
|
+
|
68
|
+
# Requery Gandi fo this mailbox's information.
|
69
|
+
# @return [GandiV5::Email::Mailbox]
|
70
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
71
|
+
def refresh
|
72
|
+
data = GandiV5.get url
|
73
|
+
from_gandi data
|
74
|
+
end
|
75
|
+
|
76
|
+
# Update the mailbox's settings.
|
77
|
+
# @see https://api.gandi.net/docs/email#patch-v5-email-mailboxes-domain-mailbox_id
|
78
|
+
# @param login [String, #to_s] the login name (and first part of email address).
|
79
|
+
# @param password [String, #to_s] the password to use.
|
80
|
+
# @param aliases [Array<String, #to_s>] any alternative email address to be used.
|
81
|
+
# @param responder [Hash, GandiV5::Mailbox::Responder, #to_gandi, #to_h]
|
82
|
+
# auto responder settings.
|
83
|
+
# @return [String] The confirmation message from Gandi.
|
84
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
85
|
+
# rubocop:disable Metrics/AbcSize
|
86
|
+
def update(**body)
|
87
|
+
return 'Nothing to update.' if body.empty?
|
88
|
+
|
89
|
+
check_password body[:password] if body.key?(:password)
|
90
|
+
|
91
|
+
body[:password] = crypt_password(body[:password]) if body.key?(:password)
|
92
|
+
if (responder = body[:responder])
|
93
|
+
body[:responder] = responder.respond_to?(:to_gandi) ? responder.to_gandi : responder.to_h
|
94
|
+
end
|
95
|
+
|
96
|
+
data = GandiV5.patch url, body.to_json
|
97
|
+
refresh
|
98
|
+
data['message']
|
99
|
+
end
|
100
|
+
# rubocop:enable Metrics/AbcSize
|
101
|
+
|
102
|
+
# Create a new mailbox.
|
103
|
+
# Note that before you can create a mailbox, you must have a slot available.
|
104
|
+
# @see https://api.gandi.net/docs/email#post-v5-email-mailboxes-domain
|
105
|
+
# @param fqdn [String, #to_s] the fully qualified domain name for the mailbox.
|
106
|
+
# @param login [String, #to_s] the login name (and first part of email address).
|
107
|
+
# @param password [String, #to_s] the password to use.
|
108
|
+
# @param aliases [Array<String, #to_s>] any alternative email address to be used.
|
109
|
+
# @param type [:standard, :premium] the type of mailbox slot to use.
|
110
|
+
# @return [String] The confirmation message from Gandi.
|
111
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
112
|
+
# TODO: Fetch created mailbox
|
113
|
+
def self.create(fqdn, login, password, aliases: [], type: :standard)
|
114
|
+
# TODO: Check type is valid
|
115
|
+
check_password password
|
116
|
+
# TODO: Check if a slot is available
|
117
|
+
|
118
|
+
body = {
|
119
|
+
mailbox_type: type,
|
120
|
+
login: login,
|
121
|
+
password: crypt_password(password),
|
122
|
+
aliases: aliases.push
|
123
|
+
}.to_json
|
124
|
+
|
125
|
+
data = GandiV5.post url(fqdn), body
|
126
|
+
data['message']
|
127
|
+
end
|
128
|
+
|
129
|
+
# Get information for a mailbox.
|
130
|
+
# @see https://api.gandi.net/docs/email#get-v5-email-mailboxes-domain-mailbox_id
|
131
|
+
# @param fqdn [String, #to_s] the fully qualified domain name for the mailbox.
|
132
|
+
# @param uuid [String, #to_s] unique identifier of the mailbox.
|
133
|
+
# @return [GandiV5::Email::Mailbox]
|
134
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
135
|
+
def self.fetch(fqdn, uuid)
|
136
|
+
data = GandiV5.get url(fqdn, uuid)
|
137
|
+
from_gandi data
|
138
|
+
end
|
139
|
+
|
140
|
+
# List mailboxes for a domain.
|
141
|
+
# @see https://api.gandi.net/docs/email#get-v5-email-mailboxes-domain
|
142
|
+
# @param fqdn [String, #to_s] the fully qualified domain name for the mailboxes.
|
143
|
+
# @param page [Integer, #each<Integer>] which page(s) of results to get.
|
144
|
+
# If page is not provided keep querying until an empty list is returned.
|
145
|
+
# If page responds to .each then iterate until an empty list is returned.
|
146
|
+
# @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
|
147
|
+
# @param sort_by [#to_s] (optional default "login")
|
148
|
+
# how to sort the results ("login", "-login").
|
149
|
+
# @param login [String] (optional) filter the list by login (pattern)
|
150
|
+
# e.g. ("alice" "*lice", "alic*").
|
151
|
+
# @return [Array<GandiV5::Email::Mailbox>]
|
152
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
153
|
+
def self.list(fqdn, page: (1..), **params)
|
154
|
+
page = [page.to_i] unless page.respond_to?(:each)
|
155
|
+
|
156
|
+
params['~login'] = params.delete(:login)
|
157
|
+
params.reject! { |_k, v| v.nil? }
|
158
|
+
|
159
|
+
mailboxes = []
|
160
|
+
page.each do |page_number|
|
161
|
+
data = GandiV5.get url(fqdn), params: params.merge(page: page_number)
|
162
|
+
break if data.empty?
|
163
|
+
|
164
|
+
mailboxes += data.map { |mailbox| from_gandi mailbox }
|
165
|
+
break if data.count < params.fetch(:per_page, 100)
|
166
|
+
end
|
167
|
+
mailboxes
|
168
|
+
end
|
169
|
+
|
170
|
+
# Get the quota for this type of mailbox.
|
171
|
+
# @return [Integer] bytes.
|
172
|
+
def quota
|
173
|
+
QUOTAS[type]
|
174
|
+
end
|
175
|
+
|
176
|
+
# Get the quota usage for this mailbox.
|
177
|
+
# @return [Float] fraction of quota used (typically between 0.0 and 1.0)
|
178
|
+
def quota_usage
|
179
|
+
quota_used.to_f / quota
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns the string representation of the mailbox.
|
183
|
+
# Includes the type, address, quota usage, activeness of responder (if present)
|
184
|
+
# and aliases (if present).
|
185
|
+
# @return [String]
|
186
|
+
def to_s
|
187
|
+
s = "[#{type}] #{address} (#{quota_used}/#{quota} (#{(quota_usage * 100).round}%))"
|
188
|
+
s += " with #{responder.active? ? 'active' : 'inactive'} responder" if responder
|
189
|
+
s += " aka: #{aliases.join(', ')}" if aliases&.any?
|
190
|
+
s
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
# rubocop:disable Style/GuardClause
|
196
|
+
def self.check_password(password)
|
197
|
+
if !(9..200).cover?(password.length)
|
198
|
+
fail ArgumentError, 'password must be between 9 and 200 characters'
|
199
|
+
elsif password.count('A-Z') < 1
|
200
|
+
fail ArgumentError, 'password must contain at least one upper case character'
|
201
|
+
elsif password.count('0-9') < 3
|
202
|
+
fail ArgumentError, 'password must contain at least three numbers'
|
203
|
+
elsif password.count('^a-z^A-Z^0-9') < 1
|
204
|
+
fail ArgumentError, 'password must contain at least one special character'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
private_class_method :check_password
|
208
|
+
# rubocop:enable Style/GuardClause
|
209
|
+
|
210
|
+
def check_password(password)
|
211
|
+
self.class.send :check_password, password
|
212
|
+
end
|
213
|
+
|
214
|
+
def url
|
215
|
+
"#{BASE}email/mailboxes/#{CGI.escape fqdn}/#{CGI.escape uuid}"
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.url(fqdn, uuid = nil)
|
219
|
+
"#{BASE}email/mailboxes/#{CGI.escape fqdn}" +
|
220
|
+
(uuid ? "/#{CGI.escape uuid}" : '')
|
221
|
+
end
|
222
|
+
private_class_method :url
|
223
|
+
|
224
|
+
def self.crypt_password(password)
|
225
|
+
# You can also send a hashed password in sha512-crypt ie: {SHA512-CRYPT}$6$xxxx$yyyy
|
226
|
+
salt = SecureRandom.random_number(36**8).to_s(36)
|
227
|
+
password.crypt('$6$' + salt)
|
228
|
+
end
|
229
|
+
private_class_method :crypt_password
|
230
|
+
|
231
|
+
def crypt_password(password)
|
232
|
+
self.class.send :crypt_password, password
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GandiV5
|
4
|
+
class Email
|
5
|
+
# The current status of your mailbox offer.
|
6
|
+
# @!attribute [r] status
|
7
|
+
# @return [:active, :inactive]
|
8
|
+
# @!attribute [r] version
|
9
|
+
# @return [1, 2]
|
10
|
+
class Offer
|
11
|
+
include GandiV5::Data
|
12
|
+
|
13
|
+
member :version
|
14
|
+
member :status, converter: GandiV5::Data::Converter::Symbol
|
15
|
+
|
16
|
+
# Get the current status of your mailbox offer.
|
17
|
+
# @see https://api.gandi.net/docs/email#get-v5-email-offers-domain
|
18
|
+
# @param fqdn [String, #to_s] the fully qualified domain name to get the offer for.
|
19
|
+
# @return [GandiV5::Email::Offer]
|
20
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
21
|
+
def self.fetch(fqdn)
|
22
|
+
data = GandiV5.get "#{BASE}email/offers/#{CGI.escape fqdn}"
|
23
|
+
from_gandi data
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GandiV5
|
4
|
+
class Email
|
5
|
+
# A slot is attached to a domain and (optionally) contains a mailbox.
|
6
|
+
# There must be an available slot for a mailbox to be created.
|
7
|
+
# @!attribute [r] capacity
|
8
|
+
# @return [Integer] slot capacity (in MB).
|
9
|
+
# @!attribute [r] created_at
|
10
|
+
# @return [Time]
|
11
|
+
# @!attribute [r] id
|
12
|
+
# @return [Integer]
|
13
|
+
# @!attribute [r] mailbox_type
|
14
|
+
# @return [:standard, :premium]
|
15
|
+
# @!attribute [r] status
|
16
|
+
# @return [:active, :inactive]
|
17
|
+
# @!attribute [r] refundable
|
18
|
+
# @return [Boolean]
|
19
|
+
# @!attribute [r] refund_amount
|
20
|
+
# @return [nil, Numeric] refunded amount if you delete this slot now.
|
21
|
+
# @!attribute [r] refund_currency
|
22
|
+
# @return [nil, String] refund currency.
|
23
|
+
class Slot
|
24
|
+
include GandiV5::Data
|
25
|
+
|
26
|
+
attr_reader :fqdn
|
27
|
+
|
28
|
+
members :id, :refundable, :refund_amount, :refund_currency
|
29
|
+
member(
|
30
|
+
:capacity,
|
31
|
+
converter: GandiV5::Data::Converter.new(
|
32
|
+
from_gandi: ->(value) { value * 1_024**2 }
|
33
|
+
)
|
34
|
+
)
|
35
|
+
member :created_at, converter: GandiV5::Data::Converter::Time
|
36
|
+
member :mailbox_type, converter: GandiV5::Data::Converter::Symbol
|
37
|
+
member :status, converter: GandiV5::Data::Converter::Symbol
|
38
|
+
|
39
|
+
alias slot_id id
|
40
|
+
|
41
|
+
# Create a new GandiV5::Email::Slot
|
42
|
+
# @param string [fqdn] the fully qualified domain this slot belongs to.
|
43
|
+
# @param members [Hash<Symbol => Object>]
|
44
|
+
# @return [GandiV5::Email::Slot]
|
45
|
+
def initialize(fqdn: nil, **members)
|
46
|
+
super(**members)
|
47
|
+
@fqdn = fqdn if fqdn
|
48
|
+
end
|
49
|
+
|
50
|
+
# Delete this slot if it is inactive and refundable.
|
51
|
+
# When you delete a slot, the prepaid account that was used to purchase the slot
|
52
|
+
# will be refunded for the remaining time that will not be used.
|
53
|
+
# @see GandiV5::Email::Mailbox#delete
|
54
|
+
# @see https://api.gandi.net/docs/email#delete-v5-email-slots-domain-slot_id
|
55
|
+
# @return [String] The confirmation message from Gandi.
|
56
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
57
|
+
# TODO: check for inactiveness
|
58
|
+
# TODO: check for refundableness
|
59
|
+
def delete
|
60
|
+
data = GandiV5.delete url
|
61
|
+
data['message']
|
62
|
+
end
|
63
|
+
|
64
|
+
# Requery Gandi for this slot's information.
|
65
|
+
# @see https://api.gandi.net/docs/email#get-v5-email-slots-domain-slot_id
|
66
|
+
# @return [GandiV5::Email::Slot]
|
67
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
68
|
+
def refresh
|
69
|
+
data = GandiV5.get url
|
70
|
+
from_gandi data
|
71
|
+
end
|
72
|
+
|
73
|
+
# Creates a new slot. You must have slots available before you can create a mailbox.
|
74
|
+
# If you have used the two free standard 3GB mailbox slots that are included with the domain,
|
75
|
+
# but require more mailboxes on that domain, you must first purchase additional slots.
|
76
|
+
# @see https://api.gandi.net/docs/email#post-v5-email-slots-domain
|
77
|
+
# @param fqdn [String, #to_s] the fully qualified domain name to add the slot to.
|
78
|
+
# @param type [:standard, :premium] Tyhe type of slot to add.
|
79
|
+
# @return [String] The confirmation message from Gandi.
|
80
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
81
|
+
# TODO: Fetch created slot
|
82
|
+
def self.create(fqdn, type = :standard)
|
83
|
+
body = {
|
84
|
+
mailbox_type: type
|
85
|
+
}.to_json
|
86
|
+
|
87
|
+
data = GandiV5.post url(fqdn), body
|
88
|
+
data['message']
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get information for a slot.
|
92
|
+
# @see https://api.gandi.net/docs/email#get-v5-email-slots-domain-slot_id
|
93
|
+
# @param fqdn [String, #to_s] the fully qualified domain name the slot is on.
|
94
|
+
# @param id [String, #to_s] the ID of the slot to fetch.
|
95
|
+
# @return [GandiV5::Email::Slot]
|
96
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
97
|
+
def self.fetch(fqdn, id)
|
98
|
+
data = GandiV5.get url(fqdn, id)
|
99
|
+
slot = from_gandi data
|
100
|
+
slot.instance_eval { @fqdn = fqdn }
|
101
|
+
slot
|
102
|
+
end
|
103
|
+
|
104
|
+
# List slots for a domain.
|
105
|
+
# @see https://api.gandi.net/docs/email#
|
106
|
+
# @param fqdn [String, #to_s] the fully qualified domain name to list slots for.
|
107
|
+
# @return [Array<GandiV5::Email::Slot>]
|
108
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
109
|
+
def self.list(fqdn)
|
110
|
+
data = GandiV5.get url(fqdn)
|
111
|
+
data.map { |item| from_gandi item }
|
112
|
+
.each { |item| item.instance_eval { @fqdn = fqdn } }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Check if the slot is active (in use)
|
116
|
+
# @return [Boolean]
|
117
|
+
def active?
|
118
|
+
status.eql?(:active)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def url
|
124
|
+
"#{BASE}email/slots/#{CGI.escape fqdn}/#{id}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.url(fqdn, id = nil)
|
128
|
+
"#{BASE}email/slots/#{CGI.escape fqdn}" +
|
129
|
+
(id ? "/#{id}" : '')
|
130
|
+
end
|
131
|
+
private_class_method :url
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GandiV5
|
4
|
+
class Error < RuntimeError
|
5
|
+
# Generic error class for errors returned by Gandi.
|
6
|
+
class GandiError < GandiV5::Error
|
7
|
+
# Generate a new GandiV5::Error::GandiError::GandiError from the hash returned by Gandi.
|
8
|
+
# @param hash [Hash] the hash returned by Gandi.
|
9
|
+
# @return [GandiV5::Error::GandiError::GandiError]
|
10
|
+
def self.from_hash(hash)
|
11
|
+
hash['errors'] ||= []
|
12
|
+
|
13
|
+
new(
|
14
|
+
(hash['errors'].count > 1 ? "\n" : '') +
|
15
|
+
hash['errors'].map { |err| "#{err['location']}->#{err['name']}: #{err['description']}" }
|
16
|
+
.join("\n")
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GandiV5
|
4
|
+
class LiveDNS
|
5
|
+
# A domain name within the LiveDNS system.
|
6
|
+
# @!attribute [r] fqdn
|
7
|
+
# @return [String]
|
8
|
+
# @!attribute [r] zone_uuid
|
9
|
+
# @return [String]
|
10
|
+
class Domain
|
11
|
+
include GandiV5::Data
|
12
|
+
|
13
|
+
members :fqdn
|
14
|
+
|
15
|
+
member(
|
16
|
+
:zone_uuid,
|
17
|
+
gandi_key: 'zone',
|
18
|
+
converter: GandiV5::Data::Converter.new(from_gandi: ->(zone) { zone&.split('/')&.last })
|
19
|
+
)
|
20
|
+
|
21
|
+
# Refetch the information for this domain from Gandi.
|
22
|
+
# @return [GandiV5::LiveDNS::Domain]
|
23
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
24
|
+
def refresh
|
25
|
+
data = GandiV5.get url
|
26
|
+
from_gandi data
|
27
|
+
end
|
28
|
+
|
29
|
+
# @overload fetch_records()
|
30
|
+
# Fetch all records for this domain.
|
31
|
+
# @overload fetch_records(name)
|
32
|
+
# Fetch records for a name.
|
33
|
+
# @param name [String] the name to fetch records for.
|
34
|
+
# @overload fetch_records(name, type)
|
35
|
+
# Fetch records of a type for a name.
|
36
|
+
# @param name [String] the name to fetch records for.
|
37
|
+
# @param type [String] the record type to fetch.
|
38
|
+
# @return [Array<GandiV5::LiveDNS::RecordSet>]
|
39
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
40
|
+
def fetch_records(name = nil, type = nil)
|
41
|
+
GandiV5::LiveDNS.require_valid_record_type type if type
|
42
|
+
|
43
|
+
url_ = "#{url}/records"
|
44
|
+
url_ += "/#{CGI.escape name}" if name
|
45
|
+
url_ += "/#{CGI.escape type}" if type
|
46
|
+
|
47
|
+
data = GandiV5.get url_
|
48
|
+
data = [data] unless data.is_a?(Array)
|
49
|
+
data.map { |item| GandiV5::LiveDNS::RecordSet.from_gandi item }
|
50
|
+
end
|
51
|
+
|
52
|
+
# @overload fetch_zone_lines()
|
53
|
+
# Fetch all records for this domain.
|
54
|
+
# @overload fetch_zone_lines(name)
|
55
|
+
# Fetch records for a name.
|
56
|
+
# @param name [String] the name to fetch records for.
|
57
|
+
# @overload fetch_zone_lines(name, type)
|
58
|
+
# Fetch records of a type for a name.
|
59
|
+
# @param name [String] the name to fetch records for.
|
60
|
+
# @param type [String] the record type to fetch.
|
61
|
+
# @return [String]
|
62
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
63
|
+
def fetch_zone_lines(name = nil, type = nil)
|
64
|
+
GandiV5::LiveDNS.require_valid_record_type type if type
|
65
|
+
|
66
|
+
url_ = "#{url}/records"
|
67
|
+
url_ += "/#{CGI.escape name}" if name
|
68
|
+
url_ += "/#{CGI.escape type}" if type
|
69
|
+
|
70
|
+
GandiV5.get url_, accept: 'text/plain'
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add record to this domain.
|
74
|
+
# @param name [String]
|
75
|
+
# @param type [String]
|
76
|
+
# @param ttl [Integer]
|
77
|
+
# @param values [Array<String>]
|
78
|
+
# @return [String] The confirmation message from Gandi.
|
79
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
80
|
+
def add_record(name, type, ttl, *values)
|
81
|
+
GandiV5::LiveDNS.require_valid_record_type type
|
82
|
+
fail ArgumentError, 'ttl must be positive and non-zero' unless ttl.positive?
|
83
|
+
fail ArgumentError, 'there must be at least one value' if values.none?
|
84
|
+
|
85
|
+
body = {
|
86
|
+
rrset_name: name,
|
87
|
+
rrset_type: type,
|
88
|
+
rrset_ttl: ttl,
|
89
|
+
rrset_values: values
|
90
|
+
}.to_json
|
91
|
+
data = GandiV5.post "#{url}/records", body
|
92
|
+
data['message']
|
93
|
+
end
|
94
|
+
|
95
|
+
# @overload delete_records()
|
96
|
+
# Delete all records for this domain.
|
97
|
+
# @overload delete_records(name)
|
98
|
+
# Delete records for a name.
|
99
|
+
# @param name [String] the name to delete records for.
|
100
|
+
# @overload delete_records(name, type)
|
101
|
+
# Delete records of a type for a name.
|
102
|
+
# @param name [String] the name to delete records for.
|
103
|
+
# @param type [String] the record type to delete.
|
104
|
+
# @return [nil]
|
105
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
106
|
+
def delete_records(name = nil, type = nil)
|
107
|
+
GandiV5::LiveDNS.require_valid_record_type(type) if type
|
108
|
+
|
109
|
+
url_ = "#{url}/records"
|
110
|
+
url_ += "/#{CGI.escape name}" if name
|
111
|
+
url_ += "/#{CGI.escape type}" if type
|
112
|
+
GandiV5.delete url_
|
113
|
+
end
|
114
|
+
|
115
|
+
# Replace all records for this domain.
|
116
|
+
# @param records
|
117
|
+
# [Array<Hash<:name, :type => String, :ttl => Integer, :values => Array<String>>>]
|
118
|
+
# the records to add.
|
119
|
+
# @param text [String] zone file lines to replace the records with.
|
120
|
+
# @return [String] The confirmation message from Gandi.
|
121
|
+
# @raise [ArgumentError] if neither/both of records & test are passed.
|
122
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
123
|
+
def replace_records(records: nil, text: nil)
|
124
|
+
unless [records, text].count(&:nil?).eql?(1)
|
125
|
+
fail ArgumentError, 'you must pass ONE of records: or text:'
|
126
|
+
end
|
127
|
+
|
128
|
+
if records
|
129
|
+
body = {
|
130
|
+
items: records.map { |r| r.transform_keys { |k| "rrset_#{k}" } }
|
131
|
+
}.to_json
|
132
|
+
data = GandiV5.put "#{url}/records", body
|
133
|
+
elsif text
|
134
|
+
data = GandiV5.put "#{url}/records", text, 'content-type': 'text/plain'
|
135
|
+
end
|
136
|
+
data['message']
|
137
|
+
end
|
138
|
+
|
139
|
+
# Replace records for a name in this domain.
|
140
|
+
# @param name [String]
|
141
|
+
# @param records
|
142
|
+
# [Array<Hash<type: String, ttl: Integer, values: Array<String>>>]
|
143
|
+
# the records to add.
|
144
|
+
# @return [String] The confirmation message from Gandi.
|
145
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
146
|
+
def replace_records_for(name, *records)
|
147
|
+
body = {
|
148
|
+
items: records.map { |r| r.transform_keys { |k| "rrset_#{k}" } }
|
149
|
+
}.to_json
|
150
|
+
data = GandiV5.put "#{url}/records/#{name}", body
|
151
|
+
data['message']
|
152
|
+
end
|
153
|
+
|
154
|
+
GandiV5::LiveDNS::RECORD_TYPES.each do |type|
|
155
|
+
# Replace records of a given type for a name in this domain.
|
156
|
+
# TODO: @param name [Type] description.
|
157
|
+
# TODO: @param ttl [Type] description.
|
158
|
+
# TODO: documentation for *values
|
159
|
+
# @return [String] The confirmation message from Gandi.
|
160
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
161
|
+
define_method "replace_#{type.downcase}_records_for" do |name, ttl, *values|
|
162
|
+
body = {
|
163
|
+
rrset_ttl: ttl,
|
164
|
+
rrset_values: values
|
165
|
+
}.to_json
|
166
|
+
data = GandiV5.put "#{url}/records/#{name}/#{type}", body
|
167
|
+
data['message']
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Change the zone used by this domain.
|
172
|
+
# @param uuid [String, #uuid, #to_s] the UUID of the zone this domain should now use.
|
173
|
+
# @return [String] The confirmation message from Gandi.
|
174
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
175
|
+
def change_zone(uuid)
|
176
|
+
uuid = uuid.uuid if uuid.respond_to?(:uuid)
|
177
|
+
data = GandiV5.patch url, { zone_uuid: uuid }.to_json
|
178
|
+
self.zone_uuid = uuid
|
179
|
+
data['message']
|
180
|
+
end
|
181
|
+
|
182
|
+
# List the domains.
|
183
|
+
# @return [Array<GandiV5::LiveDNS::Domain>]
|
184
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
185
|
+
def self.list
|
186
|
+
data = GandiV5.get url
|
187
|
+
data.map { |item| from_gandi item }
|
188
|
+
end
|
189
|
+
|
190
|
+
# Get a domain.
|
191
|
+
# @param fqdn [String, #to_s] the fully qualified domain name to fetch.
|
192
|
+
# @return [GandiV5::LiveDNS::Domain]
|
193
|
+
# @raise [GandiV5::Error::GandiError::GandiError] if Gandi returns an error.
|
194
|
+
def self.fetch(fqdn)
|
195
|
+
data = GandiV5.get url(fqdn)
|
196
|
+
from_gandi data
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def url
|
202
|
+
"#{BASE}domains/#{CGI.escape(fqdn)}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.url(fqdn = nil)
|
206
|
+
"#{BASE}domains" + (fqdn ? "/#{CGI.escape(fqdn)}" : '')
|
207
|
+
end
|
208
|
+
private_class_method :url
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|