gandi_v5 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +13 -3
  4. data/.travis.yml +15 -1
  5. data/CHANGELOG.md +63 -0
  6. data/FUNDING.yml +10 -0
  7. data/LICENSE.md +2 -6
  8. data/README.md +16 -21
  9. data/gandi_v5.gemspec +3 -2
  10. data/lib/gandi_v5.rb +64 -24
  11. data/lib/gandi_v5/billing.rb +0 -2
  12. data/lib/gandi_v5/billing/info.rb +0 -2
  13. data/lib/gandi_v5/data.rb +1 -4
  14. data/lib/gandi_v5/data/converter.rb +0 -4
  15. data/lib/gandi_v5/data/converter/integer.rb +26 -0
  16. data/lib/gandi_v5/domain.rb +167 -28
  17. data/lib/gandi_v5/domain/availability.rb +0 -3
  18. data/lib/gandi_v5/domain/availability/product.rb +0 -3
  19. data/lib/gandi_v5/domain/live_dns.rb +42 -0
  20. data/lib/gandi_v5/domain/renewal_information.rb +0 -3
  21. data/lib/gandi_v5/domain/sharing_space.rb +10 -2
  22. data/lib/gandi_v5/email.rb +0 -4
  23. data/lib/gandi_v5/email/forward.rb +102 -0
  24. data/lib/gandi_v5/email/mailbox.rb +57 -13
  25. data/lib/gandi_v5/email/slot.rb +11 -3
  26. data/lib/gandi_v5/error.rb +0 -2
  27. data/lib/gandi_v5/live_dns.rb +0 -16
  28. data/lib/gandi_v5/live_dns/domain.rb +221 -80
  29. data/lib/gandi_v5/live_dns/domain/dnssec_key.rb +115 -0
  30. data/lib/gandi_v5/live_dns/domain/record.rb +81 -0
  31. data/lib/gandi_v5/live_dns/domain/snapshot.rb +107 -0
  32. data/lib/gandi_v5/live_dns/domain/tsig_key.rb +71 -0
  33. data/lib/gandi_v5/organization.rb +38 -5
  34. data/lib/gandi_v5/organization/customer.rb +90 -0
  35. data/lib/gandi_v5/version.rb +1 -1
  36. data/spec/.rubocop.yml +9 -2
  37. data/spec/fixtures/bodies/GandiV5_Billing/{info.yaml → info.yml} +0 -0
  38. data/spec/fixtures/bodies/GandiV5_Domain/{get.yaml → fetch.yml} +8 -0
  39. data/spec/fixtures/bodies/GandiV5_Domain/{fetch_contacts.yaml → fetch_contacts.yml} +0 -0
  40. data/spec/fixtures/bodies/GandiV5_Domain/fetch_glue_records.yml +7 -0
  41. data/spec/fixtures/bodies/GandiV5_Domain/fetch_livedns.yml +6 -0
  42. data/spec/fixtures/bodies/GandiV5_Domain/fetch_name_servers.yml +2 -0
  43. data/spec/fixtures/bodies/GandiV5_Domain/{renewal_info.yaml → fetch_renewal_info.yml} +0 -3
  44. data/spec/fixtures/bodies/GandiV5_Domain/{restore_info.yaml → fetch_restore_info.yml} +0 -0
  45. data/spec/fixtures/bodies/GandiV5_Domain/{list.yaml → list.yml} +1 -0
  46. data/spec/fixtures/bodies/GandiV5_Domain_Availability/{fetch.yaml → fetch.yml} +0 -0
  47. data/spec/fixtures/bodies/GandiV5_Domain_TLD/{fetch.yaml → fetch.yml} +0 -0
  48. data/spec/fixtures/bodies/GandiV5_Domain_TLD/{list.yaml → list.yml} +0 -0
  49. data/spec/fixtures/bodies/GandiV5_Email_Forward/list.yml +6 -0
  50. data/spec/fixtures/bodies/GandiV5_Email_Mailbox/{get.yaml → fetch.yml} +0 -0
  51. data/spec/fixtures/bodies/GandiV5_Email_Mailbox/{list.yaml → list.yml} +0 -0
  52. data/spec/fixtures/bodies/GandiV5_Email_Slot/{get.yaml → fetch.yml} +0 -0
  53. data/spec/fixtures/bodies/GandiV5_Email_Slot/{list.yaml → list.yml} +0 -0
  54. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/fetch.yml +3 -0
  55. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/{list.yaml → list.yml} +0 -0
  56. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/list_tsig.yml +3 -0
  57. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/nameservers.yml +3 -0
  58. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_DnssecKey/fetch.yml +12 -0
  59. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_DnssecKey/list.yml +9 -0
  60. data/spec/fixtures/bodies/{GandiV5_LiveDNS_Zone_Snapshot/fetch.yaml → GandiV5_LiveDNS_Domain_Snapshot/fetch.yml} +4 -3
  61. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_Snapshot/list.yml +5 -0
  62. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_TsigKey/fetch.yml +9 -0
  63. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_TsigKey/list.yml +4 -0
  64. data/spec/fixtures/bodies/GandiV5_Organization/{get.yaml → fetch.yml} +0 -0
  65. data/spec/fixtures/bodies/GandiV5_Organization/list.yml +7 -0
  66. data/spec/fixtures/bodies/GandiV5_Organization_Customer/list.yml +8 -0
  67. data/spec/spec_helper.rb +2 -2
  68. data/spec/units/gandi_v5/billing_spec.rb +2 -2
  69. data/spec/units/gandi_v5/data/converter/integer_spec.rb +16 -0
  70. data/spec/units/gandi_v5/domain/availability_spec.rb +1 -1
  71. data/spec/units/gandi_v5/domain/live_dns_spec.rb +45 -0
  72. data/spec/units/gandi_v5/domain/tld_spec.rb +2 -2
  73. data/spec/units/gandi_v5/domain_spec.rb +294 -65
  74. data/spec/units/gandi_v5/email/forward_spec.rb +92 -0
  75. data/spec/units/gandi_v5/email/mailbox_spec.rb +122 -40
  76. data/spec/units/gandi_v5/email/slot_spec.rb +13 -5
  77. data/spec/units/gandi_v5/live_dns/domain/dnssec_key_spec.rb +128 -0
  78. data/spec/units/gandi_v5/live_dns/{record_set_spec.rb → domain/record_spec.rb} +1 -1
  79. data/spec/units/gandi_v5/live_dns/domain/snapshot_spec.rb +101 -0
  80. data/spec/units/gandi_v5/live_dns/domain/tsig_key_spec.rb +78 -0
  81. data/spec/units/gandi_v5/live_dns/domain_spec.rb +299 -120
  82. data/spec/units/gandi_v5/live_dns_spec.rb +0 -12
  83. data/spec/units/gandi_v5/organization/customer_spec.rb +81 -0
  84. data/spec/units/gandi_v5/organization_spec.rb +52 -1
  85. data/spec/units/gandi_v5_spec.rb +139 -30
  86. metadata +68 -95
  87. data/doc/GandiV5.html +0 -1183
  88. data/doc/GandiV5/Billing.html +0 -293
  89. data/doc/GandiV5/Billing/Info.html +0 -641
  90. data/doc/GandiV5/Billing/Info/Prepaid.html +0 -817
  91. data/doc/GandiV5/Data.html +0 -785
  92. data/doc/GandiV5/Data/ClassMethods.html +0 -223
  93. data/doc/GandiV5/Data/Converter.html +0 -433
  94. data/doc/GandiV5/Data/Converter/ArrayOf.html +0 -413
  95. data/doc/GandiV5/Data/Converter/Symbol.html +0 -322
  96. data/doc/GandiV5/Data/Converter/Time.html +0 -330
  97. data/doc/GandiV5/Domain.html +0 -16847
  98. data/doc/GandiV5/Domain/AutoRenew.html +0 -1237
  99. data/doc/GandiV5/Domain/Availability.html +0 -1020
  100. data/doc/GandiV5/Domain/Availability/Product.html +0 -988
  101. data/doc/GandiV5/Domain/Availability/Product/Period.html +0 -220
  102. data/doc/GandiV5/Domain/Availability/Product/Price.html +0 -1031
  103. data/doc/GandiV5/Domain/Availability/Tax.html +0 -440
  104. data/doc/GandiV5/Domain/Contact.html +0 -4459
  105. data/doc/GandiV5/Domain/Contract.html +0 -520
  106. data/doc/GandiV5/Domain/Dates.html +0 -1313
  107. data/doc/GandiV5/Domain/RenewalInformation.html +0 -1147
  108. data/doc/GandiV5/Domain/RestoreInformation.html +0 -339
  109. data/doc/GandiV5/Domain/SharingSpace.html +0 -437
  110. data/doc/GandiV5/Domain/TLD.html +0 -1565
  111. data/doc/GandiV5/Email.html +0 -144
  112. data/doc/GandiV5/Email/Mailbox.html +0 -6307
  113. data/doc/GandiV5/Email/Mailbox/Responder.html +0 -1560
  114. data/doc/GandiV5/Email/Offer.html +0 -514
  115. data/doc/GandiV5/Email/Slot.html +0 -4244
  116. data/doc/GandiV5/Error.html +0 -151
  117. data/doc/GandiV5/Error/GandiError.html +0 -270
  118. data/doc/GandiV5/LiveDNS.html +0 -300
  119. data/doc/GandiV5/LiveDNS/Domain.html +0 -2984
  120. data/doc/GandiV5/LiveDNS/RecordSet.html +0 -1593
  121. data/doc/GandiV5/LiveDNS/Zone.html +0 -8891
  122. data/doc/GandiV5/LiveDNS/Zone/Snapshot.html +0 -1556
  123. data/doc/GandiV5/Organization.html +0 -2341
  124. data/doc/_index.html +0 -474
  125. data/doc/class_list.html +0 -51
  126. data/doc/css/common.css +0 -1
  127. data/doc/css/full_list.css +0 -58
  128. data/doc/css/style.css +0 -496
  129. data/doc/file.README.html +0 -175
  130. data/doc/file_list.html +0 -56
  131. data/doc/frames.html +0 -17
  132. data/doc/index.html +0 -175
  133. data/doc/js/app.js +0 -303
  134. data/doc/js/full_list.js +0 -216
  135. data/doc/js/jquery.js +0 -4
  136. data/doc/method_list.html +0 -2427
  137. data/doc/top-level-namespace.html +0 -110
  138. data/lib/gandi_v5/live_dns/record_set.rb +0 -79
  139. data/lib/gandi_v5/live_dns/zone.rb +0 -305
  140. data/lib/gandi_v5/live_dns/zone/snapshot.rb +0 -81
  141. data/spec/features/domain_spec.rb +0 -45
  142. data/spec/features/livedns_domain_spec.rb +0 -8
  143. data/spec/features/livedns_zone_spec.rb +0 -45
  144. data/spec/features/mailbox_spec.rb +0 -18
  145. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/get.yaml +0 -4
  146. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/get.yaml +0 -11
  147. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/list.yaml +0 -11
  148. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone_Snapshot/list.yaml +0 -3
  149. data/spec/fixtures/vcr/Domain_features/List_domains.yml +0 -54
  150. data/spec/fixtures/vcr/Domain_features/Renew_domain.yml +0 -133
  151. data/spec/fixtures/vcr/LiveDNS_Domain_features/List_domains.yml +0 -32
  152. data/spec/fixtures/vcr/LiveDNS_Zone_features/List_zones.yml +0 -42
  153. data/spec/fixtures/vcr/LiveDNS_Zone_features/Make_and_save_snapshot.yml +0 -72
  154. data/spec/fixtures/vcr/LiveDNS_Zone_features/Save_zone_to_file.yml +0 -28
  155. data/spec/fixtures/vcr/Mailbox_features/List_mailboxes.yml +0 -39
  156. data/spec/units/gandi_v5/live_dns/zone/snapshot_spec.rb +0 -66
  157. data/spec/units/gandi_v5/live_dns/zone_spec.rb +0 -347
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'mailbox/responder'
4
-
5
3
  class GandiV5
6
4
  class Email
7
5
  # A mailbox that lives within a domain.
@@ -107,6 +105,39 @@ class GandiV5
107
105
  data['message']
108
106
  end
109
107
 
108
+ # Upgrade a standard mailbox to premium.
109
+ # If the current slot is a free one, a new premium slot is created and
110
+ # used for the mailbox. Otherwise, the slot is upgraded to premium.
111
+ # @see https://api.gandi.net/docs/email#patch-v5-email-mailboxes-domain-mailbox_id-type
112
+ # @param sharing_id [String, #to_s, nil] (optional)
113
+ # the organisation ID to bill for the mailbox.
114
+ # @param dry_run [Boolean] whether the details should be checked instead
115
+ # of actually upgrading the mailbox.
116
+ # @return [true] if the mailbox was upgraded
117
+ # @return [false] if the mailbox was not upgraded (it's already premium)
118
+ # @return [Hash] if doing a dry run, you get what Gandi returns
119
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
120
+ def upgrade(sharing_id: nil, dry_run: false)
121
+ patch_type :premium, sharing_id, dry_run
122
+ end
123
+
124
+ # Downgrade a premium mailbox to standard.
125
+ # If a free slot is available, the premium slot is destroyed
126
+ # (and refunded) and the free one is used for the mailbox.
127
+ # Otherwise, the slot is downgraded to standard.
128
+ # @see https://api.gandi.net/docs/email#patch-v5-email-mailboxes-domain-mailbox_id-type
129
+ # @param sharing_id [String, #to_s, nil] (optional)
130
+ # the organisation ID to bill for the mailbox.
131
+ # @param dry_run [Boolean] whether the details should be checked instead
132
+ # of actually downgrading the mailbox.
133
+ # @return [true] if the mailbox was downgraded
134
+ # @return [false] if the mailbox was not downgraded (it's already standard)
135
+ # @return [Hash] if doing a dry run, you get what Gandi returns
136
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
137
+ def downgrade(sharing_id: nil, dry_run: false)
138
+ patch_type :standard, sharing_id, dry_run
139
+ end
140
+
110
141
  # Create a new mailbox.
111
142
  # Note that before you can create a mailbox, you must have a slot available.
112
143
  # @see https://api.gandi.net/docs/email#post-v5-email-mailboxes-domain
@@ -115,10 +146,13 @@ class GandiV5
115
146
  # @param password [String, #to_s] the password to use.
116
147
  # @param aliases [Array<String, #to_s>] any alternative email address to be used.
117
148
  # @param type [:standard, :premium] the type of mailbox slot to use.
149
+ # @param dry_run [Boolean] whether the details should be checked instead
150
+ # of actually creating the mailbox.
118
151
  # @return [GandiV5::Email::Mailbox] The created mailbox.
119
152
  # @raise [GandiV5::Error] if no slots are available.
120
153
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
121
- def self.create(fqdn, login, password, aliases: [], type: :standard)
154
+ # rubocop:disable Metrics/AbcSize
155
+ def self.create(fqdn, login, password, aliases: [], type: :standard, dry_run: false)
122
156
  fail ArgumentError, "#{type.inspect} is not a valid type" unless TYPES.include?(type)
123
157
  if GandiV5::Email::Slot.list.none? { |slot| slot.mailbox_type == type && slot.inactive? }
124
158
  fail GandiV5::Error, "no available #{type} slots"
@@ -133,9 +167,11 @@ class GandiV5
133
167
  aliases: aliases.push
134
168
  }.to_json
135
169
 
136
- response, _data = GandiV5.post url(fqdn), body
137
- fetch fqdn, response.headers[:location].split('/').last
170
+ response, data = GandiV5.post(url(fqdn), body, 'Dry-Run': dry_run ? 1 : 0)
171
+
172
+ dry_run ? data : fetch(fqdn, response.headers[:location].split('/').last)
138
173
  end
174
+ # rubocop:enable Metrics/AbcSize
139
175
 
140
176
  # Get information for a mailbox.
141
177
  # @see https://api.gandi.net/docs/email#get-v5-email-mailboxes-domain-mailbox_id
@@ -161,19 +197,13 @@ class GandiV5
161
197
  # e.g. ("alice" "*lice", "alic*").
162
198
  # @return [Array<GandiV5::Email::Mailbox>]
163
199
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
164
- def self.list(fqdn, page: (1..), **params)
165
- page = [page.to_i] unless page.respond_to?(:each)
166
-
200
+ def self.list(fqdn, page: (1..), per_page: 100, **params)
167
201
  params['~login'] = params.delete(:login)
168
202
  params.reject! { |_k, v| v.nil? }
169
203
 
170
204
  mailboxes = []
171
- page.each do |page_number|
172
- _response, data = GandiV5.get url(fqdn), params: params.merge(page: page_number)
173
- break if data.empty?
174
-
205
+ GandiV5.paginated_get(url(fqdn), page, per_page, params: params) do |data|
175
206
  mailboxes += data.map { |mailbox| from_gandi mailbox }
176
- break if data.count < params.fetch(:per_page, 100)
177
207
  end
178
208
  mailboxes
179
209
  end
@@ -242,6 +272,20 @@ class GandiV5
242
272
  def crypt_password(password)
243
273
  self.class.send :crypt_password, password
244
274
  end
275
+
276
+ def patch_type(new_type, sharing_id, dry_run)
277
+ fail ArgumentError unless TYPES.include?(new_type)
278
+ return false if type == new_type
279
+
280
+ url_ = "#{url}/type"
281
+ url_ = sharing_id ? "#{url_}?sharing_id=#{sharing_id}" : url_
282
+ body = { mailbox_type: new_type }
283
+
284
+ _response, data = GandiV5.patch(url_, body.to_json, 'Dry-Run': dry_run ? 1 : 0)
285
+
286
+ @type = new_type unless dry_run
287
+ dry_run ? data : true
288
+ end
245
289
  end
246
290
  end
247
291
  end
@@ -78,15 +78,23 @@ class GandiV5
78
78
  # but require more mailboxes on that domain, you must first purchase additional slots.
79
79
  # @see https://api.gandi.net/docs/email#post-v5-email-slots-domain
80
80
  # @param fqdn [String, #to_s] the fully qualified domain name to add the slot to.
81
- # @param type [:standard, :premium] Tyhe type of slot to add.
81
+ # @param type [:standard, :premium] The type of slot to add.
82
+ # @param sharing_id [nil, String, #to_s] either:
83
+ # * nil (default) - nothing special happens
84
+ # * an organization ID - pay using another organization
85
+ # (you need to have billing permissions on the organization
86
+ # and use the same organization name for the domain name's owner).
87
+ # The invoice will be edited using this organization's information.
82
88
  # @return [String] The confirmation message from Gandi.
83
89
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
84
- def self.create(fqdn, type = :standard)
90
+ def self.create(fqdn, type: :standard, sharing_id: nil)
85
91
  body = {
86
92
  mailbox_type: type
87
93
  }.to_json
88
94
 
89
- response, _data = GandiV5.post url(fqdn), body
95
+ url_ = url(fqdn)
96
+ url_ += "?sharing_id=#{sharing_id}" if sharing_id
97
+ response, _data = GandiV5.post url_, body
90
98
  fetch fqdn, response.headers[:location].split('/').last
91
99
  end
92
100
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'error/gandi_error'
4
-
5
3
  class GandiV5
6
4
  # Generic error class for errors occuring using the API.
7
5
  class Error < RuntimeError
@@ -4,8 +4,6 @@
4
4
  class GandiV5
5
5
  # Gandi LiveDNS Management API.
6
6
  class LiveDNS
7
- BASE = 'https://dns.api.gandi.net/api/v5/'
8
-
9
7
  RECORD_TYPES = %w[
10
8
  A AAAA CNAME MX NS TXT ALIAS
11
9
  WKS SRV LOC SPF CAA DS SSHFP PTR KEY DNAME TLSA OPENPGPKEY CDS
@@ -21,16 +19,6 @@ class GandiV5
21
19
  GandiV5::LiveDNS::Domain.list
22
20
  end
23
21
 
24
- # @see GandiV5::LiveDNS::Zone.fetch
25
- def self.zone(uuid)
26
- GandiV5::LiveDNS::Zone.fetch(uuid)
27
- end
28
-
29
- # @see GandiV5::LiveDNS::Zone.list
30
- def self.zones
31
- GandiV5::LiveDNS::Zone.list
32
- end
33
-
34
22
  # Raise an error if passed type is invalid.
35
23
  # @param type [String] the record type to check.
36
24
  # @return [nil]
@@ -44,7 +32,3 @@ class GandiV5
44
32
  # rubocop:enable Style/GuardClause
45
33
  end
46
34
  end
47
-
48
- require_relative 'live_dns/record_set'
49
- require_relative 'live_dns/domain'
50
- require_relative 'live_dns/zone'
@@ -5,18 +5,12 @@ class GandiV5
5
5
  # A domain name within the LiveDNS system.
6
6
  # @!attribute [r] fqdn
7
7
  # @return [String]
8
- # @!attribute [r] zone_uuid
9
- # @return [String]
8
+ # @!attribute [r] automatic_snapshots
9
+ # @return [Boolean]
10
10
  class Domain
11
11
  include GandiV5::Data
12
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
- )
13
+ members :fqdn, :automatic_snapshots
20
14
 
21
15
  # Refetch the information for this domain from Gandi.
22
16
  # @return [GandiV5::LiveDNS::Domain]
@@ -26,27 +20,52 @@ class GandiV5
26
20
  from_gandi data
27
21
  end
28
22
 
23
+ # Update this domain's settings.
24
+ # @param automatic_snapshots [String, #to_s]
25
+ # Enable or disable the automatic creation of new snapshots when records are changed.
26
+ # @return [String] The confirmation message from Gandi.
27
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
28
+ def update(automatic_snapshots:)
29
+ _response, data = GandiV5.patch url, { automatic_snapshots: automatic_snapshots }.to_json
30
+ self.automatic_snapshots = automatic_snapshots
31
+ data['message']
32
+ end
33
+
29
34
  # @overload fetch_records()
30
35
  # Fetch all records for this domain.
36
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
37
+ # If page is not provided keep querying until an empty list is returned.
38
+ # If page responds to .each then iterate until an empty list is returned.
39
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
31
40
  # @overload fetch_records(name)
32
41
  # Fetch records for a name.
33
42
  # @param name [String] the name to fetch records for.
43
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
44
+ # If page is not provided keep querying until an empty list is returned.
45
+ # If page responds to .each then iterate until an empty list is returned.
46
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
34
47
  # @overload fetch_records(name, type)
35
48
  # Fetch records of a type for a name.
36
49
  # @param name [String] the name to fetch records for.
37
50
  # @param type [String] the record type to fetch.
38
- # @return [Array<GandiV5::LiveDNS::RecordSet>]
51
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
52
+ # If page is not provided keep querying until an empty list is returned.
53
+ # If page responds to .each then iterate until an empty list is returned.
54
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
55
+ # @return [Array<GandiV5::LiveDNS::Domain::Record>]
39
56
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
40
- def fetch_records(name = nil, type = nil)
57
+ def fetch_records(name = nil, type = nil, page: (1..), per_page: 100)
41
58
  GandiV5::LiveDNS.require_valid_record_type type if type
42
59
 
43
60
  url_ = "#{url}/records"
44
61
  url_ += "/#{CGI.escape name}" if name
45
62
  url_ += "/#{CGI.escape type}" if type
46
63
 
47
- _response, data = GandiV5.get url_
48
- data = [data] unless data.is_a?(Array)
49
- data.map { |item| GandiV5::LiveDNS::RecordSet.from_gandi item }
64
+ all = []
65
+ GandiV5.paginated_get(url_, page, per_page) do |data|
66
+ all += [*data].map { |item| GandiV5::LiveDNS::Domain::Record.from_gandi item }
67
+ end
68
+ all
50
69
  end
51
70
 
52
71
  # @overload fetch_zone_lines()
@@ -112,93 +131,172 @@ class GandiV5
112
131
  GandiV5.delete(url_).last
113
132
  end
114
133
 
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.
134
+ # Replace records for the domain.
135
+ # @param name [String, nil] only replaces records for this name.
136
+ # @param type [String, nil] only replaces record of this type (requires name).
137
+ # @param values [Array<String>] the values to set for the record.
138
+ # @raise [ArgumentError] if ttl is present and type is absent.
120
139
  # @return [String] The confirmation message from Gandi.
121
- # @raise [ArgumentError] if neither/both of records & test are passed.
122
140
  # @raise [GandiV5::Error::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:'
141
+ # rubocop:disable Metrics/PerceivedComplexity
142
+ def replace_records(records, name: nil, type: nil)
143
+ if type
144
+ GandiV5::LiveDNS.require_valid_record_type(type) if type
145
+ fail ArgumentError, 'missing keyword: name' if name.nil?
126
146
  end
127
147
 
128
- if records
129
- body = {
130
- items: records.map { |r| r.transform_keys { |k| "rrset_#{k}" } }
131
- }.to_json
132
- _response, data = GandiV5.put "#{url}/records", body
133
- elsif text
134
- _response, data = GandiV5.put "#{url}/records", text, 'content-type': 'text/plain'
135
- end
148
+ url_ = "#{url}/records"
149
+ url_ += "/#{CGI.escape name}" if name
150
+ url_ += "/#{CGI.escape type}" if type
151
+
152
+ body = if type && name
153
+ { rrset_values: records }
154
+ else
155
+ { items: records.map { |r| r.transform_keys { |k| "rrset_#{k}" } } }
156
+ end
157
+
158
+ _response, data = GandiV5.put url_, body.to_json
136
159
  data['message']
137
160
  end
161
+ # rubocop:enable Metrics/PerceivedComplexity
138
162
 
139
- # @override replace_records_for(name, records)
140
- # Replace records for a name in this domain.
141
- # @param name [String]
142
- # @param records
143
- # [Array<Hash<type: String, ttl: Integer, values: Array<String>>>]
144
- # the records to add.
145
- # @override replace_records_for(name, values, type: nil, ttl: nil)
146
- # Replace records for a name in this domain.
147
- # @param name [String]
148
- # @param type [String] the record type.
149
- # @param ttl [Integer] the TTL to set for the record.
150
- # @param values [Array<String>] the values to set for the record.
151
- # @raise [ArgumentError] if ttl is present and type is absent.
163
+ # Replace all records for this domain.
164
+ # @param text [String] zone file lines to replace the records with.
152
165
  # @return [String] The confirmation message from Gandi.
153
166
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
154
- def replace_records_for(name, records, type: nil, ttl: nil)
155
- fail ArgumentError, 'missing keyword: type' if ttl && type.nil?
167
+ def replace_zone_lines(text)
168
+ _response, data = GandiV5.put "#{url}/records", text, 'content-type': 'text/plain'
169
+ data['message']
170
+ end
156
171
 
157
- if type
158
- GandiV5::LiveDNS.require_valid_record_type type
159
- body = { rrset_values: records, rrset_ttl: ttl }
160
- # body[:rrset_ttl] = ttl if ttl
161
- _response, data = GandiV5.put "#{url}/records/#{name}/#{type}", body.to_json
162
-
163
- else
164
- body = {
165
- items: records.map { |r| r.transform_keys { |k| "rrset_#{k}" } }
166
- }
167
- _response, data = GandiV5.put "#{url}/records/#{name}", body.to_json
168
- end
172
+ # The list of nameservers that this domain is using according to LiveDNS' systems.
173
+ # * Either there are no NS records on @ and the 3 hashed nameservers are returned
174
+ # (ns-{123}-{abc}.gandi.net)
175
+ # * Or some NS records exist on @ and it will return those
176
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-domains-domain-name$
177
+ # @return [Array<String>]
178
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
179
+ def name_servers
180
+ @name_servers ||= fetch_name_servers
181
+ end
169
182
 
170
- data['message']
183
+ # Requery Gandi for the domain's name servers.
184
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-domains-domain-name$
185
+ # @return [Array<String>]
186
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
187
+ def fetch_name_servers
188
+ _response, data = GandiV5.get "#{url}/nameservers"
189
+ @name_servers = data
171
190
  end
172
191
 
173
- # Change the zone used by this domain.
174
- # @param uuid [String, #uuid, #to_s] the UUID of the zone this domain should now use.
175
- # @return [String] The confirmation message from Gandi.
192
+ # The list of DNSSEC keys for the domain.
193
+ # If you need the fingerprint, public_key or tag attributes you'll need
194
+ # use GandiV5::LiveDNS::Domain::DnssecKey.fetch on each item.
195
+ # @return [Array<GandiV5::LiveDNS::Domain::DnssecKey>]
176
196
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
177
- def change_zone(uuid)
178
- uuid = uuid.uuid if uuid.respond_to?(:uuid)
179
- _response, data = GandiV5.patch url, { zone_uuid: uuid }.to_json
180
- self.zone_uuid = uuid
181
- data['message']
197
+ def dnssec_keys
198
+ @dnssec_keys ||= fetch_dnssec_keys
199
+ end
200
+
201
+ # Requery Gandi for the domain's DNSSEC keys.
202
+ # @return [Array<GandiV5::LiveDNS::Domain::DnssecKey>]
203
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
204
+ def fetch_dnssec_keys
205
+ @dnssec_keys = GandiV5::LiveDNS::Domain::DnssecKey.list(fqdn)
182
206
  end
183
207
 
184
- # @see GandiV5::LiveDNS::Zone.fetch
185
- def fetch_zone
186
- GandiV5::LiveDNS::Zone.fetch zone_uuid
208
+ # The list of TSIG keys for the domain.
209
+ # If you need the secret, fingerprint, public_key or tag attributes you'll need
210
+ # to use GandiV5::LiveDNS::Domain::DnssecKey.fetch on each item.
211
+ # @return [Array<GandiV5::LiveDNS::Domain::TsigKey>]
212
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
213
+ def tsig_keys
214
+ @tsig_keys ||= fetch_tsig_keys
215
+ end
216
+
217
+ # Requery Gandi for the domain's TSIG keys.
218
+ # @return [Array<GandiV5::LiveDNS::Domain::TsigKey>]
219
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
220
+ def fetch_tsig_keys
221
+ _response, data = GandiV5.get "#{url}/axfr/tsig"
222
+ data.map { |item| GandiV5::LiveDNS::Domain::TsigKey.from_gandi item }
223
+ end
224
+
225
+ # Add a Tsig key to this domain.
226
+ # @param key [GandiV5::LiveDNS::Domain::TsigKey, #uuid, String, #to_s]
227
+ # the key to add.
228
+ # @param sharing_id [nil, String, #to_s]
229
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
230
+ def add_tsig_key(key, sharing_id: nil)
231
+ key = key.uuid if key.respond_to?(:uuid)
232
+ url_ = "#{url}/axfr/tsig/#{key}"
233
+ url_ += "?sharing_id=#{CGI.escape sharing_id}" if sharing_id
234
+ _response, _data = GandiV5.put url_
235
+ end
236
+
237
+ # Remove a Tsig key from this domain.
238
+ # @param key [GandiV5::LiveDNS::Domain::TsigKey, #uuid, String, #to_s]
239
+ # the key to remove.
240
+ # @param sharing_id [nil, String, #to_s]
241
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
242
+ def remove_tsig_key(key, sharing_id: nil)
243
+ key = key.uuid if key.respond_to?(:uuid)
244
+ url_ = "#{url}/axfr/tsig/#{key}"
245
+ url_ += "?sharing_id=#{CGI.escape sharing_id}" if sharing_id
246
+ _response, _data = GandiV5.delete url_
247
+ end
248
+
249
+ # The list of AXFR clients for the domain.
250
+ # @return [Array<String>] list of IP addresses.
251
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
252
+ def axfr_clients
253
+ @axfr_clients ||= fetch_axfr_clients
254
+ end
255
+
256
+ # Requery Gandi for the domain's AXFR clients.
257
+ # @return [Array<String>] list of IP addresses.
258
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
259
+ def fetch_axfr_clients
260
+ _response, data = GandiV5.get "#{url}/axfr/slaves"
261
+ data
262
+ end
263
+
264
+ # Add an AXFR client to this domain.
265
+ # @param ip [String, #to_s] the IP address to add.
266
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
267
+ def add_axfr_client(ip)
268
+ _response, _data = GandiV5.put "#{url}/axfr/slaves/#{ip}"
187
269
  end
188
270
 
189
- # The domain's zone (fetching from Gandi if required).
190
- # @return [GandiV5::LiveDNS::Zone]
271
+ # Remove and AXFR client from this domain.
272
+ # @param ip [String, #to_s] the IP address to remove.
191
273
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
192
- def zone
193
- @zone ||= fetch_zone
274
+ def remove_axfr_client(ip)
275
+ _response, _data = GandiV5.delete "#{url}/axfr/slaves/#{ip}"
194
276
  end
195
277
 
196
278
  # List the domains.
197
- # @return [Array<GandiV5::LiveDNS::Domain>]
279
+ # @param page [#each<Integer, #to_s>] the page(s) of results to retrieve.
280
+ # If page is not provided keep querying until an empty list is returned.
281
+ # If page responds to .each then iterate until an empty list is returned.
282
+ # @param per_page [Integer, #to_s] (optional default 100) how many results to get per page.
283
+ # @param sharing_id [String, #to_s] (optional) filter by domains assigned to a given user.
284
+ # @return [Array<String>]
198
285
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
199
- def self.list
200
- _response, data = GandiV5.get url
201
- data.map { |item| from_gandi item }
286
+ def self.list(page: (1..), per_page: 100, sharing_id: nil)
287
+ page = [page.to_i] unless page.respond_to?(:each)
288
+ params = { per_page: per_page }
289
+ params[:sharing_id] = sharing_id unless sharing_id.nil?
290
+
291
+ domains = []
292
+ page.each do |page_number|
293
+ _resp, data = GandiV5.get url, params: params.merge(page: page_number)
294
+ break if data.empty?
295
+
296
+ domains += data.map { |item| item['fqdn'] }
297
+ break if data.count < per_page
298
+ end
299
+ domains
202
300
  end
203
301
 
204
302
  # Get a domain.
@@ -210,14 +308,57 @@ class GandiV5
210
308
  from_gandi data
211
309
  end
212
310
 
311
+ # Create a new domain in the LiveDNS system.
312
+ # You must have sufficent permission to manage the domain to do this.
313
+ # @param fqdn [String, #to_s] the fully qualified domain to add to LiveDNS.
314
+ # @param records [Array<Hash, GandiV5::LiveDNS::Domain::Record, #to_h, nil>]
315
+ # @param ttl [Integer, #to_s, nil] the TTL of the SOA record.
316
+ # Note that this is not a default TTL that will be used for the records in the zone.
317
+ # the records (if any) to add to the created zone.
318
+ # @param sharing_id [nil, String, #to_s]
319
+ # @return [GandiV5::LiveDNS::Domain]
320
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
321
+ def self.create(fqdn, records = nil, soa_ttl: nil, sharing_id: nil)
322
+ body = { fqdn: fqdn, zone: {} }
323
+ body[:zone][:ttl] = soa_ttl if soa_ttl
324
+ if records
325
+ body[:zone][:items] = records.map do |r|
326
+ r.to_h.transform_keys { |k| "rrset_#{k}" }
327
+ end
328
+ end
329
+
330
+ url_ = url
331
+ url_ += "?sharing_id=#{CGI.escape sharing_id}" if sharing_id
332
+
333
+ GandiV5.post url_, body.to_json
334
+ fetch(fqdn)
335
+ end
336
+
337
+ # Fetch the list of known record types (A, CNAME, etc.)
338
+ # @return [Array<String>]
339
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
340
+ def self.record_types
341
+ GandiV5.get("#{BASE}livedns/dns/rrtypes").last
342
+ end
343
+
344
+ # Get the LiveDNS servers to use for a domain.
345
+ # Does not take into account any NS records that exist in the zone.
346
+ # @param fqdn [String, #to_s] the fully qualified domain to hash in
347
+ # in order to get the LiveDNS servers to use.
348
+ # @return [Array<String>]
349
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
350
+ def self.generic_name_servers(fqdn)
351
+ GandiV5.get("#{BASE}livedns/nameservers/#{CGI.escape fqdn}").last
352
+ end
353
+
213
354
  private
214
355
 
215
356
  def url
216
- "#{BASE}domains/#{CGI.escape(fqdn)}"
357
+ "#{BASE}livedns/domains/#{CGI.escape fqdn}"
217
358
  end
218
359
 
219
360
  def self.url(fqdn = nil)
220
- "#{BASE}domains" + (fqdn ? "/#{CGI.escape(fqdn)}" : '')
361
+ "#{BASE}livedns/domains" + (fqdn ? "/#{CGI.escape fqdn}" : '')
221
362
  end
222
363
  private_class_method :url
223
364
  end