gandi_v5 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|