gandi_v5 0.6.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -5
  3. data/README.md +65 -14
  4. data/lib/gandi_v5.rb +139 -70
  5. data/lib/gandi_v5/billing/info/prepaid.rb +1 -0
  6. data/lib/gandi_v5/data.rb +3 -2
  7. data/lib/gandi_v5/data/converter.rb +3 -2
  8. data/lib/gandi_v5/data/converter/array_of.rb +3 -2
  9. data/lib/gandi_v5/data/converter/integer.rb +3 -2
  10. data/lib/gandi_v5/data/converter/symbol.rb +3 -2
  11. data/lib/gandi_v5/data/converter/time.rb +3 -2
  12. data/lib/gandi_v5/domain.rb +55 -18
  13. data/lib/gandi_v5/domain/availability/product/period.rb +1 -1
  14. data/lib/gandi_v5/domain/contact.rb +5 -5
  15. data/lib/gandi_v5/domain/tld.rb +2 -2
  16. data/lib/gandi_v5/domain/transfer_in.rb +172 -0
  17. data/lib/gandi_v5/domain/transfer_in/availability.rb +51 -0
  18. data/lib/gandi_v5/domain/web_forwarding.rb +182 -0
  19. data/lib/gandi_v5/email.rb +3 -0
  20. data/lib/gandi_v5/email/forward.rb +3 -9
  21. data/lib/gandi_v5/email/mailbox.rb +5 -11
  22. data/lib/gandi_v5/error/gandi_error.rb +1 -0
  23. data/lib/gandi_v5/live_dns.rb +2 -12
  24. data/lib/gandi_v5/live_dns/domain.rb +340 -29
  25. data/lib/gandi_v5/live_dns/domain/dnssec_key.rb +120 -0
  26. data/lib/gandi_v5/live_dns/domain/record.rb +81 -0
  27. data/lib/gandi_v5/live_dns/domain/snapshot.rb +111 -0
  28. data/lib/gandi_v5/live_dns/domain/tsig_key.rb +74 -0
  29. data/lib/gandi_v5/sharing_space.rb +27 -0
  30. data/lib/gandi_v5/simple_hosting.rb +13 -0
  31. data/lib/gandi_v5/simple_hosting/instance.rb +251 -0
  32. data/lib/gandi_v5/simple_hosting/instance/application.rb +45 -0
  33. data/lib/gandi_v5/simple_hosting/instance/database.rb +20 -0
  34. data/lib/gandi_v5/simple_hosting/instance/language.rb +22 -0
  35. data/lib/gandi_v5/simple_hosting/instance/upgrade.rb +22 -0
  36. data/lib/gandi_v5/simple_hosting/instance/virtual_host.rb +272 -0
  37. data/lib/gandi_v5/simple_hosting/instance/virtual_host/linked_dns_zone.rb +75 -0
  38. data/lib/gandi_v5/template.rb +271 -0
  39. data/lib/gandi_v5/template/dispatch.rb +109 -0
  40. data/lib/gandi_v5/template/payload.rb +64 -0
  41. data/lib/gandi_v5/template/payload/dns_record.rb +23 -0
  42. data/lib/gandi_v5/template/payload/web_forwarding.rb +82 -0
  43. data/lib/gandi_v5/version.rb +1 -1
  44. data/spec/.rubocop.yml +9 -9
  45. data/spec/features/list_domain_renewals_spec.rb +16 -0
  46. data/spec/features/list_email_addresses_spec.rb +39 -0
  47. data/spec/fixtures/bodies/GandiV5_Domain_TransferIn/fetch.yml +21 -0
  48. data/spec/fixtures/bodies/GandiV5_Domain_TransferIn_Availability/fetch.yml +10 -0
  49. data/spec/fixtures/bodies/GandiV5_Domain_WebForwarding/fetch.yml +9 -0
  50. data/spec/fixtures/bodies/GandiV5_Domain_WebForwarding/list.yml +9 -0
  51. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/fetch.yml +1 -2
  52. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/list_tsig.yml +3 -0
  53. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain/nameservers.yml +3 -0
  54. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_DnssecKey/fetch.yml +12 -0
  55. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_DnssecKey/list.yml +9 -0
  56. data/spec/fixtures/bodies/{GandiV5_LiveDNS_Zone_Snapshot → GandiV5_LiveDNS_Domain_Snapshot}/fetch.yml +4 -3
  57. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_Snapshot/list.yml +5 -0
  58. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_TsigKey/fetch.yml +9 -0
  59. data/spec/fixtures/bodies/GandiV5_LiveDNS_Domain_TsigKey/list.yml +4 -0
  60. data/spec/fixtures/bodies/GandiV5_SimpleHosting_Instance/fetch.yml +80 -0
  61. data/spec/fixtures/bodies/GandiV5_SimpleHosting_Instance/list.yml +38 -0
  62. data/spec/fixtures/bodies/GandiV5_SimpleHosting_Instance_VirtualHost/fetch.yml +26 -0
  63. data/spec/fixtures/bodies/GandiV5_SimpleHosting_Instance_VirtualHost/list.yml +18 -0
  64. data/spec/fixtures/bodies/GandiV5_Template/fetch.yml +41 -0
  65. data/spec/fixtures/bodies/GandiV5_Template/list.yml +20 -0
  66. data/spec/fixtures/bodies/GandiV5_Template_Dispatch/fetch.yml +49 -0
  67. data/spec/fixtures/vcr/Examples/List_domain_renewals.yml +54 -0
  68. data/spec/fixtures/vcr/Examples/List_email_addresses.yml +103 -0
  69. data/spec/spec_helper.rb +8 -7
  70. data/spec/units/gandi_v5/domain/transfer_in/availability_spec.rb +49 -0
  71. data/spec/units/gandi_v5/domain/transfer_in_spec.rb +143 -0
  72. data/spec/units/gandi_v5/domain/web_forwarding_spec.rb +150 -0
  73. data/spec/units/gandi_v5/domain_spec.rb +56 -37
  74. data/spec/units/gandi_v5/email/forward_spec.rb +5 -34
  75. data/spec/units/gandi_v5/email/mailbox_spec.rb +4 -34
  76. data/spec/units/gandi_v5/live_dns/domain/dnssec_key_spec.rb +128 -0
  77. data/spec/units/gandi_v5/live_dns/{record_set_spec.rb → domain/record_spec.rb} +1 -1
  78. data/spec/units/gandi_v5/live_dns/domain/snapshot_spec.rb +101 -0
  79. data/spec/units/gandi_v5/live_dns/domain/tsig_key_spec.rb +78 -0
  80. data/spec/units/gandi_v5/live_dns/domain_spec.rb +297 -118
  81. data/spec/units/gandi_v5/live_dns_spec.rb +0 -12
  82. data/spec/units/gandi_v5/simple_hosting/instance/application_spec.rb +37 -0
  83. data/spec/units/gandi_v5/simple_hosting/instance/virtual_host/linked_dns_zone_spec.rb +50 -0
  84. data/spec/units/gandi_v5/simple_hosting/instance/virtual_host_spec.rb +324 -0
  85. data/spec/units/gandi_v5/simple_hosting/instance_spec.rb +190 -0
  86. data/spec/units/gandi_v5/simple_hosting_spec.rb +9 -0
  87. data/spec/units/gandi_v5/template/dispatch_spec.rb +70 -0
  88. data/spec/units/gandi_v5/template/payload/web_forwarding_spec.rb +44 -0
  89. data/spec/units/gandi_v5/template_spec.rb +341 -0
  90. data/spec/units/gandi_v5_spec.rb +111 -14
  91. metadata +226 -79
  92. data/.gitignore +0 -26
  93. data/.rspec +0 -3
  94. data/.rubocop.yml +0 -30
  95. data/.travis.yml +0 -38
  96. data/FUNDING.yml +0 -10
  97. data/Gemfile +0 -6
  98. data/Guardfile +0 -39
  99. data/Rakefile +0 -3
  100. data/bin/console +0 -13
  101. data/gandi_v5.gemspec +0 -42
  102. data/lib/gandi_v5/domain/sharing_space.rb +0 -29
  103. data/lib/gandi_v5/live_dns/has_zone_records.rb +0 -153
  104. data/lib/gandi_v5/live_dns/record_set.rb +0 -79
  105. data/lib/gandi_v5/live_dns/zone.rb +0 -160
  106. data/lib/gandi_v5/live_dns/zone/snapshot.rb +0 -81
  107. data/spec/features/domain_spec.rb +0 -45
  108. data/spec/features/livedns_domain_spec.rb +0 -8
  109. data/spec/features/livedns_zone_spec.rb +0 -44
  110. data/spec/features/mailbox_spec.rb +0 -18
  111. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/fetch.yml +0 -11
  112. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone/list.yml +0 -11
  113. data/spec/fixtures/bodies/GandiV5_LiveDNS_Zone_Snapshot/list.yml +0 -3
  114. data/spec/fixtures/vcr/Domain_features/List_domains.yml +0 -55
  115. data/spec/fixtures/vcr/Domain_features/Renew_domain.yml +0 -133
  116. data/spec/fixtures/vcr/LiveDNS_Domain_features/List_domains.yml +0 -32
  117. data/spec/fixtures/vcr/LiveDNS_Zone_features/List_zones.yml +0 -42
  118. data/spec/fixtures/vcr/LiveDNS_Zone_features/Make_and_save_snapshot.yml +0 -72
  119. data/spec/fixtures/vcr/LiveDNS_Zone_features/Save_zone_to_file.yml +0 -28
  120. data/spec/fixtures/vcr/Mailbox_features/List_mailboxes.yml +0 -39
  121. data/spec/units/gandi_v5/billing/info_spec.rb +0 -4
  122. data/spec/units/gandi_v5/domain/availability/product/period_spec.rb +0 -4
  123. data/spec/units/gandi_v5/domain/availability/product/price_spec.rb +0 -4
  124. data/spec/units/gandi_v5/domain/availability/product_spec.rb +0 -4
  125. data/spec/units/gandi_v5/domain/availability/tax_spec.rb +0 -4
  126. data/spec/units/gandi_v5/domain/contract_spec.rb +0 -4
  127. data/spec/units/gandi_v5/domain/dates_spec.rb +0 -4
  128. data/spec/units/gandi_v5/domain/restore_information_spec.rb +0 -4
  129. data/spec/units/gandi_v5/domain/sharing_space_spec.rb +0 -4
  130. data/spec/units/gandi_v5/error_spec.rb +0 -4
  131. data/spec/units/gandi_v5/live_dns/zone/snapshot_spec.rb +0 -66
  132. data/spec/units/gandi_v5/live_dns/zone_spec.rb +0 -347
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ class TransferIn
6
+ # Information about the availabillity of a domain to be transfered into Gandi.
7
+ # @!attribute [r] fqdn
8
+ # @return [String] the fully qualified domain name.
9
+ # @!attribute [r] fqdn_unicode
10
+ # @return [String] the fully qualified domain name in unicode.
11
+ # @!attribute [r] available
12
+ # @return [Boolean] whether the domain can be transfered.
13
+ # @!attribute [r] corporate
14
+ # @return [Boolean] Optional
15
+ # @!attribute [r] internal
16
+ # @return [Boolean] Optional
17
+ # @!attribute [r] minimum_duration
18
+ # @return [Integer] Optional the minimum duration you can reregister the domain for.
19
+ # @!attribute [r] maximum_duration
20
+ # @return [Integer] Optional the maximum duration you can reregister the domain for.
21
+ # @!attribute [r] durations
22
+ # @return [Array<Integer>] Optional the durations you can reregister the domain for.
23
+ # @!attribute [r] message
24
+ # @return [String, nil] Optional message explaining why the domain can't be transfered.
25
+ class Availability
26
+ include GandiV5::Data
27
+
28
+ members :fqdn, :available, :corporate, :internal,
29
+ :minimum_duration, :maximum_duration
30
+ member :durations, array: true
31
+ member :message, gandi_key: 'msg'
32
+ member :fqdn_unicode, gandi_key: 'fqdn_ulabel'
33
+
34
+ # Find out if a domain can be transfered to Gandi.
35
+ # @see https://api.gandi.net/docs/domains/#post-v5-domain-transferin-domain-available
36
+ # @param fqdn [String, #to_s] the fully qualified domain name to query.
37
+ # @param auth_code [String, #to_s] authorization code (if required).
38
+ # @return [GandiV5::Domain::TransferIn::Availabillity]
39
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
40
+ def self.fetch(fqdn, auth_code = nil)
41
+ url = "#{BASE}domain/transferin/#{fqdn}/available"
42
+ body = {}
43
+ body['authinfo'] = auth_code if auth_code
44
+
45
+ _response, data = GandiV5.post url, body
46
+ from_gandi data
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GandiV5
4
+ class Domain
5
+ # Manage web forwarding.
6
+ # @!attribute [r] created_at
7
+ # @return [Time, nil]
8
+ # @!attribute [r] updated_at
9
+ # @return [Time, nil]
10
+ # @!attribute [r] type
11
+ # @return [:cloak, :http301, :http302]
12
+ # @!attribute [r] fqdn
13
+ # @return [String]
14
+ # @!attribute [r] protocol
15
+ # @return [:http, :https, :https_only, nil]
16
+ # @!attribute [r] target
17
+ # @return [String]
18
+ # @!attribute [r] cert_status
19
+ # @return [String, nil]
20
+ # @!attribute [r] cert_uuid
21
+ # @return [String, nil]
22
+ class WebForwarding
23
+ include GandiV5::Data
24
+
25
+ members :cert_uuid, :cert_status
26
+ member :target, gandi_key: 'url'
27
+ member :fqdn, gandi_key: 'host'
28
+ member :type, converter: GandiV5::Data::Converter::Symbol
29
+ member :created_at, converter: GandiV5::Data::Converter::Time
30
+ member :updated_at, converter: GandiV5::Data::Converter::Time
31
+ member(
32
+ :protocol,
33
+ converter: GandiV5::Data::Converter.new(
34
+ from_gandi: lambda { |value|
35
+ {
36
+ 'http' => :http,
37
+ 'https' => :https,
38
+ 'httpsonly' => :https_only
39
+ }.fetch(value)
40
+ }
41
+ )
42
+ )
43
+
44
+ # Update the redirection in Gandi.
45
+ # @see https://api.gandi.net/docs/domains/#patch-v5-domain-domains-domain-webredirs-host
46
+ # @param target [String, #to_s] the url to redirect to (e.g. www.example.com/path).
47
+ # @param protocol [:http, :https, :https_only]
48
+ # @param type [:cloak, :http301, :http302]
49
+ # @param override [Boolean] If true, a DNS record will be created.
50
+ # When the value is false and no matching DNS record exists, it will trigger an error.
51
+ # @return [GandiV5::Domain::WebRedirection]
52
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
53
+ def update(target: nil, protocol: nil, type: nil, override: nil)
54
+ body = {}
55
+ body['url'] = target.to_s unless target.nil?
56
+ body['protocol'] = protocol.to_s.delete('_') unless protocol.nil?
57
+ body['type'] = type.to_s unless type.nil?
58
+ body['override'] = override unless override.nil?
59
+
60
+ GandiV5.patch url, body.to_json
61
+ refresh
62
+ end
63
+
64
+ # Delete this web redirection from Gandi.
65
+ # @see https://api.gandi.net/docs/domains/#delete-v5-domain-domains-domain-webredirs-host
66
+ # @return [String] The confirmation message from Gandi.
67
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
68
+ def delete
69
+ _response, data = GandiV5.delete url
70
+ data['message']
71
+ end
72
+
73
+ # Check if this is an HTTP 301 (permanent) redirection.
74
+ def http301?
75
+ type == :http301
76
+ end
77
+
78
+ # Check if this is an HTTP 302 (found) redirection.
79
+ def http302?
80
+ type == :http302
81
+ end
82
+
83
+ # Check if this is an HTTP 301 (permanent) redirection.
84
+ def permanent?
85
+ type == :http301
86
+ end
87
+
88
+ # Check if this is an HTTP 302 (found) redirection.
89
+ def found?
90
+ type == :http302
91
+ end
92
+
93
+ # Check if this is a temporary redirection.
94
+ def temporary?
95
+ type == :http302
96
+ end
97
+
98
+ # Check if it's an http end point
99
+ def http?
100
+ protocol == :http || protocol == :https
101
+ end
102
+
103
+ # Check if it's an https end point
104
+ def https?
105
+ protocol == :https || protocol == :https_only
106
+ end
107
+
108
+ # Check if it's an https only
109
+ def https_only?
110
+ protocol == :https_only
111
+ end
112
+
113
+ # Create a new web redirection.
114
+ # @see https://api.gandi.net/docs/domains/#post-v5-domain-domains-domain-webredirs
115
+ # @param domain [String, #to_s] the domain name to create the redirection in.
116
+ # @param host [String, #to_s] the host name to redirect from.
117
+ # @param target [String, #to_s] the url to redirect to (e.g. www.example.com/path).
118
+ # @param protocol [:http, :https, :https_only]
119
+ # @param type [:cloak, :http301, :http302]
120
+ # @param override [Boolean] When you create a redirection on a domain, a DNS record is created
121
+ # if it does not exist. When the record already exists and this parameter is set to true it
122
+ # will overwrite the record. Otherwise it will trigger an error.
123
+ # @return [GandiV5::Domain::WebRedirection] the created redirection
124
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error
125
+ def self.create(domain:, host:, target:, protocol:, type:, override: false)
126
+ body = {
127
+ 'host' => host.to_s,
128
+ 'protocol' => protocol.to_s.delete('_'),
129
+ 'type' => type.to_s,
130
+ 'url' => target.to_s,
131
+ 'override' => override
132
+ }.to_json
133
+
134
+ GandiV5.post url(domain), body
135
+ fetch domain, host
136
+ end
137
+
138
+ # Get web redirect for a host in a domain.
139
+ # @see https://api.gandi.net/docs/domains/#get-v5-domain-domains-domain-webredirs-host
140
+ # @param domain [String, #to_s] the domain to get the web redirection for.
141
+ # @param host [String, #to_s] the host name to get the web redirection for.
142
+ # @return [GandiV5::Domain::WebRedirect]
143
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
144
+ def self.fetch(domain, host)
145
+ _response, data = GandiV5.get url(domain, host)
146
+ redirect = from_gandi data
147
+ redirect.instance_exec { @domain = domain }
148
+ redirect
149
+ end
150
+
151
+ # List web redirects for a domain.
152
+ # @see https://api.gandi.net/docs/domains/#get-v5-domain-domains-domain-webredirs-host
153
+ # @param domain [String, #to_s] the domain to get the web redirections for.
154
+ # @param page [#each<Integer, #to_s>] the page(s) of results to retrieve.
155
+ # If page is not provided keep querying until an empty list is returned.
156
+ # If page responds to .each then iterate until an empty list is returned.
157
+ # @param per_page [Integer, #to_s] (optional default 100) how many results to get per page.
158
+ # @return [Array<GandiV5::Domain::WebRedirect>]
159
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
160
+ def self.list(domain, page: (1..), per_page: 100)
161
+ redirects = []
162
+ GandiV5.paginated_get(url(domain), page, per_page) do |data|
163
+ redirects += data.map { |redirect| from_gandi redirect }
164
+ end
165
+ redirects.each { |redirect| redirect.instance_exec { @domain = domain } }
166
+ redirects
167
+ end
168
+
169
+ private
170
+
171
+ def url
172
+ "#{BASE}domain/domains/#{CGI.escape @domain}/webredirs/#{fqdn}"
173
+ end
174
+
175
+ def self.url(domain, host = nil)
176
+ "#{BASE}domain/domains/#{CGI.escape domain}/webredirs" +
177
+ (host ? "/#{CGI.escape host}.#{CGI.escape domain}" : '')
178
+ end
179
+ private_class_method :url
180
+ end
181
+ end
182
+ end
@@ -2,6 +2,9 @@
2
2
 
3
3
  class GandiV5
4
4
  # Gandi Email Mailbox Management API.
5
+ # @see https://api.gandi.net/docs/email/
6
+ # rubocop:disable Lint/EmptyClass
5
7
  class Email
6
8
  end
9
+ # rubocop:enable Lint/EmptyClass
7
10
  end
@@ -76,18 +76,12 @@ class GandiV5
76
76
  # e.g. ("alice" "*lice", "alic*").
77
77
  # @return [Array<GandiV5::Email::Forward>]
78
78
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
79
- def self.list(fqdn, page: (1..), **params)
80
- page = [page.to_i] unless page.respond_to?(:each)
81
-
82
- params.reject! { |_k, v| v.nil? }
79
+ def self.list(fqdn, page: (1..), per_page: 100, **params)
80
+ params.compact! { |_k, v| v.nil? }
83
81
 
84
82
  mailboxes = []
85
- page.each do |page_number|
86
- _response, data = GandiV5.get url(fqdn), params: params.merge(page: page_number)
87
- break if data.empty?
88
-
83
+ GandiV5.paginated_get(url(fqdn), page, per_page, params: params) do |data|
89
84
  mailboxes += data.map { |mailbox| from_gandi mailbox.merge(fqdn: fqdn) }
90
- break if data.count < params.fetch(:per_page, 100)
91
85
  end
92
86
  mailboxes
93
87
  end
@@ -43,7 +43,7 @@ class GandiV5
43
43
  alias mailbox_uuid uuid
44
44
 
45
45
  # Create a new GandiV5::Email::Mailbox
46
- # @param members [Hash<Symbol => Object>]
46
+ # @param members [Hash{Symbol => Object}]
47
47
  # @return [GandiV5::Email::Slot]
48
48
  def initialize(**members)
49
49
  super(**members)
@@ -197,19 +197,13 @@ class GandiV5
197
197
  # e.g. ("alice" "*lice", "alic*").
198
198
  # @return [Array<GandiV5::Email::Mailbox>]
199
199
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
200
- def self.list(fqdn, page: (1..), **params)
201
- page = [page.to_i] unless page.respond_to?(:each)
202
-
200
+ def self.list(fqdn, page: (1..), per_page: 100, **params)
203
201
  params['~login'] = params.delete(:login)
204
- params.reject! { |_k, v| v.nil? }
202
+ params.compact! { |_k, v| v.nil? }
205
203
 
206
204
  mailboxes = []
207
- page.each do |page_number|
208
- _response, data = GandiV5.get url(fqdn), params: params.merge(page: page_number)
209
- break if data.empty?
210
-
205
+ GandiV5.paginated_get(url(fqdn), page, per_page, params: params) do |data|
211
206
  mailboxes += data.map { |mailbox| from_gandi mailbox }
212
- break if data.count < params.fetch(:per_page, 100)
213
207
  end
214
208
  mailboxes
215
209
  end
@@ -271,7 +265,7 @@ class GandiV5
271
265
  def self.crypt_password(password)
272
266
  # You can also send a hashed password in sha512-crypt ie: {SHA512-CRYPT}$6$xxxx$yyyy
273
267
  salt = SecureRandom.random_number(36**8).to_s(36)
274
- password.crypt('$6$' + salt)
268
+ password.crypt("$6$#{salt}")
275
269
  end
276
270
  private_class_method :crypt_password
277
271
 
@@ -5,6 +5,7 @@ class GandiV5
5
5
  # Generic error class for errors returned by Gandi.
6
6
  class GandiError < GandiV5::Error
7
7
  # Generate a new GandiV5::Error::GandiError from the hash returned by Gandi.
8
+ # @api private
8
9
  # @param hash [Hash] the hash returned by Gandi.
9
10
  # @return [GandiV5::Error::GandiError]
10
11
  def self.from_hash(hash)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Namespace for classes which access LiveDNS details.
4
+ # @see https://api.gandi.net/docs/livedns/
4
5
  class GandiV5
5
6
  # Gandi LiveDNS Management API.
6
7
  class LiveDNS
7
- BASE = 'https://dns.api.gandi.net/api/v5/'
8
-
8
+ # Permitted record types.
9
9
  RECORD_TYPES = %w[
10
10
  A AAAA CNAME MX NS TXT ALIAS
11
11
  WKS SRV LOC SPF CAA DS SSHFP PTR KEY DNAME TLSA OPENPGPKEY CDS
@@ -21,16 +21,6 @@ class GandiV5
21
21
  GandiV5::LiveDNS::Domain.list
22
22
  end
23
23
 
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
24
  # Raise an error if passed type is invalid.
35
25
  # @param type [String] the record type to check.
36
26
  # @return [nil]
@@ -5,19 +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
- include GandiV5::LiveDNS::HasZoneRecords
13
-
14
- members :fqdn
15
12
 
16
- member(
17
- :zone_uuid,
18
- gandi_key: 'zone',
19
- converter: GandiV5::Data::Converter.new(from_gandi: ->(zone) { zone&.split('/')&.last })
20
- )
13
+ members :fqdn, :automatic_snapshots
21
14
 
22
15
  # Refetch the information for this domain from Gandi.
23
16
  # @return [GandiV5::LiveDNS::Domain]
@@ -27,38 +20,310 @@ class GandiV5
27
20
  from_gandi data
28
21
  end
29
22
 
30
- # Change the zone used by this domain.
31
- # @param uuid [String, #uuid, #to_s] the UUID of the zone this domain should now use.
23
+ # Update this domain's settings.
24
+ # @see https://api.gandi.net/docs/livedns/#patch-v5-livedns-domains-fqdn
25
+ # @param automatic_snapshots [String, #to_s]
26
+ # Enable or disable the automatic creation of new snapshots when records are changed.
32
27
  # @return [String] The confirmation message from Gandi.
33
28
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
34
- def change_zone(uuid)
35
- uuid = uuid.uuid if uuid.respond_to?(:uuid)
36
- _response, data = GandiV5.patch url, { zone_uuid: uuid }.to_json
37
- self.zone_uuid = uuid
29
+ def update(automatic_snapshots:)
30
+ _response, data = GandiV5.patch url, { automatic_snapshots: automatic_snapshots }.to_json
31
+ self.automatic_snapshots = automatic_snapshots
38
32
  data['message']
39
33
  end
40
34
 
41
- # @see GandiV5::LiveDNS::Zone.fetch
42
- def fetch_zone
43
- GandiV5::LiveDNS::Zone.fetch zone_uuid
35
+ # @overload fetch_records()
36
+ # Fetch all records for this domain.
37
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-records
38
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
39
+ # If page is not provided keep querying until an empty list is returned.
40
+ # If page responds to .each then iterate until an empty list is returned.
41
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
42
+ # @overload fetch_records(name)
43
+ # Fetch records for a name.
44
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-records-rrset_name
45
+ # @param name [String] the name to fetch records for.
46
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
47
+ # If page is not provided keep querying until an empty list is returned.
48
+ # If page responds to .each then iterate until an empty list is returned.
49
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
50
+ # @overload fetch_records(name, type)
51
+ # Fetch records of a type for a name.
52
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-records-rrset_name-rrset_type
53
+ # @param name [String] the name to fetch records for.
54
+ # @param type [String] the record type to fetch.
55
+ # @param page [Integer, #each<Integer>] which page(s) of results to get.
56
+ # If page is not provided keep querying until an empty list is returned.
57
+ # If page responds to .each then iterate until an empty list is returned.
58
+ # @param per_page [Integer, #to_s] (optional default 100) how many results ot get per page.
59
+ # @return [Array<GandiV5::LiveDNS::Domain::Record>]
60
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
61
+ def fetch_records(name = nil, type = nil, page: (1..), per_page: 100)
62
+ GandiV5::LiveDNS.require_valid_record_type type if type
63
+
64
+ url_ = "#{url}/records"
65
+ url_ += "/#{CGI.escape name}" if name
66
+ url_ += "/#{CGI.escape type}" if type
67
+
68
+ all = []
69
+ GandiV5.paginated_get(url_, page, per_page) do |data|
70
+ all += [*data].map { |item| GandiV5::LiveDNS::Domain::Record.from_gandi item }
71
+ end
72
+ all
44
73
  end
45
74
 
46
- # The domain's zone (fetching from Gandi if required).
47
- # @return [GandiV5::LiveDNS::Zone]
75
+ # @overload fetch_zone_lines()
76
+ # Fetch all records for this domain.
77
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-records
78
+ # @overload fetch_zone_lines(name)
79
+ # Fetch records for a name.
80
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-records-rrset_name
81
+ # @param name [String] the name to fetch records for.
82
+ # @overload fetch_zone_lines(name, type)
83
+ # Fetch records of a type for a name.
84
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-records-rrset_name-rrset_type
85
+ # @param name [String] the name to fetch records for.
86
+ # @param type [String] the record type to fetch.
87
+ # @return [String]
48
88
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
49
- def zone
50
- @zone ||= fetch_zone
89
+ def fetch_zone_lines(name = nil, type = nil)
90
+ GandiV5::LiveDNS.require_valid_record_type type if type
91
+
92
+ url_ = "#{url}/records"
93
+ url_ += "/#{CGI.escape name}" if name
94
+ url_ += "/#{CGI.escape type}" if type
95
+
96
+ GandiV5.get(url_, accept: 'text/plain').last
97
+ end
98
+
99
+ # Add record to this domain.
100
+ # @see https://api.gandi.net/docs/livedns/#post-v5-livedns-domains-fqdn-records
101
+ # @param name [String]
102
+ # @param type [String]
103
+ # @param ttl [Integer]
104
+ # @param values [Array<String>]
105
+ # @return [String] The confirmation message from Gandi.
106
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
107
+ def add_record(name, type, ttl, *values)
108
+ GandiV5::LiveDNS.require_valid_record_type type
109
+ fail ArgumentError, 'ttl must be positive and non-zero' unless ttl.positive?
110
+ fail ArgumentError, 'there must be at least one value' if values.none?
111
+
112
+ body = {
113
+ rrset_name: name,
114
+ rrset_type: type,
115
+ rrset_ttl: ttl,
116
+ rrset_values: values
117
+ }.to_json
118
+ _response, data = GandiV5.post "#{url}/records", body
119
+ data['message']
120
+ end
121
+
122
+ # @overload delete_records()
123
+ # Delete all records for this domain.
124
+ # @see https://api.gandi.net/docs/livedns/#delete-v5-livedns-domains-fqdn-records
125
+ # @overload delete_records(name)
126
+ # Delete records for a name.
127
+ # @see https://api.gandi.net/docs/livedns/#delete-v5-livedns-domains-fqdn-records-rrset_name
128
+ # @param name [String] the name to delete records for.
129
+ # @overload delete_records(name, type)
130
+ # Delete records of a type for a name.
131
+ # @see https://api.gandi.net/docs/livedns/#delete-v5-livedns-domains-fqdn-records-rrset_name-rrset_type
132
+ # @param name [String] the name to delete records for.
133
+ # @param type [String] the record type to delete.
134
+ # @return [nil]
135
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
136
+ def delete_records(name = nil, type = nil)
137
+ GandiV5::LiveDNS.require_valid_record_type(type) if type
138
+
139
+ url_ = "#{url}/records"
140
+ url_ += "/#{CGI.escape name}" if name
141
+ url_ += "/#{CGI.escape type}" if type
142
+ GandiV5.delete(url_).last
143
+ end
144
+
145
+ # Replace records for the domain.
146
+ # @param name [String, nil] only replaces records for this name.
147
+ # @param type [String, nil] only replaces record of this type (requires name).
148
+ # @param values [Array<String>] the values to set for the record.
149
+ # @raise [ArgumentError] if ttl is present and type is absent.
150
+ # @see https://api.gandi.net/docs/livedns/#put-v5-livedns-domains-fqdn-records
151
+ # @see https://api.gandi.net/docs/livedns/#put-v5-livedns-domains-fqdn-records-rrset_name
152
+ # @see https://api.gandi.net/docs/livedns/#put-v5-livedns-domains-fqdn-records-rrset_name-rrset_type
153
+ # @return [String] The confirmation message from Gandi.
154
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
155
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
156
+ def replace_records(records, name: nil, type: nil)
157
+ if type
158
+ GandiV5::LiveDNS.require_valid_record_type(type) if type
159
+ fail ArgumentError, 'missing keyword: name' if name.nil?
160
+ end
161
+
162
+ url_ = "#{url}/records"
163
+ url_ += "/#{CGI.escape name}" if name
164
+ url_ += "/#{CGI.escape type}" if type
165
+
166
+ body = if type && name
167
+ { rrset_values: records }
168
+ else
169
+ { items: records.map { |r| r.transform_keys { |k| "rrset_#{k}" } } }
170
+ end
171
+
172
+ _response, data = GandiV5.put url_, body.to_json
173
+ data['message']
174
+ end
175
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
176
+
177
+ # Replace all records for this domain.
178
+ # @see https://api.gandi.net/docs/livedns/#put-v5-livedns-domains-fqdn-records
179
+ # @param text [String] zone file lines to replace the records with.
180
+ # @return [String] The confirmation message from Gandi.
181
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
182
+ def replace_zone_lines(text)
183
+ _response, data = GandiV5.put "#{url}/records", text, 'content-type': 'text/plain'
184
+ data['message']
185
+ end
186
+
187
+ # The list of nameservers that this domain is using according to LiveDNS' systems.
188
+ # * Either there are no NS records on @ and the 3 hashed nameservers are returned
189
+ # (ns-{123}-{abc}.gandi.net)
190
+ # * Or some NS records exist on @ and it will return those
191
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-domains-domain-name$
192
+ # @return [Array<String>]
193
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
194
+ def name_servers
195
+ @name_servers ||= fetch_name_servers
196
+ end
197
+
198
+ # Requery Gandi for the domain's name servers.
199
+ # @see https://api.gandi.net/docs/domains#get-v5-domain-domains-domain-name$
200
+ # @return [Array<String>]
201
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
202
+ def fetch_name_servers
203
+ _response, data = GandiV5.get "#{url}/nameservers"
204
+ @name_servers = data
205
+ end
206
+
207
+ # The list of DNSSEC keys for the domain.
208
+ # If you need the fingerprint, public_key or tag attributes you'll need
209
+ # use GandiV5::LiveDNS::Domain::DnssecKey.fetch on each item.
210
+ # @return [Array<GandiV5::LiveDNS::Domain::DnssecKey>]
211
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
212
+ def dnssec_keys
213
+ @dnssec_keys ||= fetch_dnssec_keys
214
+ end
215
+
216
+ # Requery Gandi for the domain's DNSSEC keys.
217
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-keys
218
+ # @return [Array<GandiV5::LiveDNS::Domain::DnssecKey>]
219
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
220
+ def fetch_dnssec_keys
221
+ @dnssec_keys = GandiV5::LiveDNS::Domain::DnssecKey.list(fqdn)
222
+ end
223
+
224
+ # The list of TSIG keys for the domain.
225
+ # If you need the secret, fingerprint, public_key or tag attributes you'll need
226
+ # to use GandiV5::LiveDNS::Domain::DnssecKey.fetch on each item.
227
+ # @return [Array<GandiV5::LiveDNS::Domain::TsigKey>]
228
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
229
+ def tsig_keys
230
+ @tsig_keys ||= fetch_tsig_keys
231
+ end
232
+
233
+ # Requery Gandi for the domain's TSIG keys.
234
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-axfr-tsig
235
+ # @return [Array<GandiV5::LiveDNS::Domain::TsigKey>]
236
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
237
+ def fetch_tsig_keys
238
+ _response, data = GandiV5.get "#{url}/axfr/tsig"
239
+ data.map { |item| GandiV5::LiveDNS::Domain::TsigKey.from_gandi item }
240
+ end
241
+
242
+ # Add a Tsig key to this domain.
243
+ # @see https://api.gandi.net/docs/livedns/#put-v5-livedns-domains-fqdn-axfr-tsig-id
244
+ # @param key [GandiV5::LiveDNS::Domain::TsigKey, #uuid, String, #to_s]
245
+ # the key to add.
246
+ # @param sharing_id [nil, String, #to_s]
247
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
248
+ def add_tsig_key(key, sharing_id: nil)
249
+ key = key.uuid if key.respond_to?(:uuid)
250
+ url_ = "#{url}/axfr/tsig/#{key}"
251
+ url_ += "?sharing_id=#{CGI.escape sharing_id}" if sharing_id
252
+ _response, _data = GandiV5.put url_
253
+ end
254
+
255
+ # Remove a Tsig key from this domain.
256
+ # @see https://api.gandi.net/docs/livedns/#delete-v5-livedns-domains-fqdn-axfr-tsig-id
257
+ # @param key [GandiV5::LiveDNS::Domain::TsigKey, #uuid, String, #to_s]
258
+ # the key to remove.
259
+ # @param sharing_id [nil, String, #to_s]
260
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
261
+ def remove_tsig_key(key, sharing_id: nil)
262
+ key = key.uuid if key.respond_to?(:uuid)
263
+ url_ = "#{url}/axfr/tsig/#{key}"
264
+ url_ += "?sharing_id=#{CGI.escape sharing_id}" if sharing_id
265
+ _response, _data = GandiV5.delete url_
266
+ end
267
+
268
+ # The list of AXFR clients for the domain.
269
+ # @return [Array<String>] list of IP addresses.
270
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
271
+ def axfr_clients
272
+ @axfr_clients ||= fetch_axfr_clients
273
+ end
274
+
275
+ # Requery Gandi for the domain's AXFR clients.
276
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn-axfr-slaves
277
+ # @return [Array<String>] list of IP addresses.
278
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
279
+ def fetch_axfr_clients
280
+ _response, data = GandiV5.get "#{url}/axfr/slaves"
281
+ data
282
+ end
283
+
284
+ # Add an AXFR client to this domain.
285
+ # @see https://api.gandi.net/docs/livedns/#put-v5-livedns-domains-fqdn-axfr-slaves-ip
286
+ # @param ip [String, #to_s] the IP address to add.
287
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
288
+ def add_axfr_client(ip)
289
+ _response, _data = GandiV5.put "#{url}/axfr/slaves/#{ip}"
290
+ end
291
+
292
+ # Remove and AXFR client from this domain.
293
+ # @see https://api.gandi.net/docs/livedns/#delete-v5-livedns-domains-fqdn-axfr-slaves-ip
294
+ # @param ip [String, #to_s] the IP address to remove.
295
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
296
+ def remove_axfr_client(ip)
297
+ _response, _data = GandiV5.delete "#{url}/axfr/slaves/#{ip}"
51
298
  end
52
299
 
53
300
  # List the domains.
54
- # @return [Array<GandiV5::LiveDNS::Domain>]
301
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains
302
+ # @param page [#each<Integer, #to_s>] the page(s) of results to retrieve.
303
+ # If page is not provided keep querying until an empty list is returned.
304
+ # If page responds to .each then iterate until an empty list is returned.
305
+ # @param per_page [Integer, #to_s] (optional default 100) how many results to get per page.
306
+ # @param sharing_id [String, #to_s] (optional) filter by domains assigned to a given user.
307
+ # @return [Array<String>]
55
308
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
56
- def self.list
57
- _response, data = GandiV5.get url
58
- data.map { |item| from_gandi item }
309
+ def self.list(page: (1..), per_page: 100, sharing_id: nil)
310
+ page = [page.to_i] unless page.respond_to?(:each)
311
+ params = { per_page: per_page }
312
+ params[:sharing_id] = sharing_id unless sharing_id.nil?
313
+
314
+ domains = []
315
+ page.each do |page_number|
316
+ _resp, data = GandiV5.get url, params: params.merge(page: page_number)
317
+ break if data.empty?
318
+
319
+ domains += data.map { |item| item['fqdn'] }
320
+ break if data.count < per_page
321
+ end
322
+ domains
59
323
  end
60
324
 
61
325
  # Get a domain.
326
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-domains-fqdn
62
327
  # @param fqdn [String, #to_s] the fully qualified domain name to fetch.
63
328
  # @return [GandiV5::LiveDNS::Domain]
64
329
  # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
@@ -67,14 +332,60 @@ class GandiV5
67
332
  from_gandi data
68
333
  end
69
334
 
335
+ # Create a new domain in the LiveDNS system.
336
+ # You must have sufficent permission to manage the domain to do this.
337
+ # @see https://api.gandi.net/docs/livedns/#post-v5-livedns-domains
338
+ # @param fqdn [String, #to_s] the fully qualified domain to add to LiveDNS.
339
+ # @param records [Array<Hash, GandiV5::LiveDNS::Domain::Record, #to_h, nil>]
340
+ # @param ttl [Integer, #to_s, nil] the TTL of the SOA record.
341
+ # Note that this is not a default TTL that will be used for the records in the zone.
342
+ # the records (if any) to add to the created zone.
343
+ # @param sharing_id [nil, String, #to_s]
344
+ # @return [GandiV5::LiveDNS::Domain]
345
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
346
+ def self.create(fqdn, records = nil, soa_ttl: nil, sharing_id: nil)
347
+ body = { fqdn: fqdn, zone: {} }
348
+ body[:zone][:ttl] = soa_ttl if soa_ttl
349
+ if records
350
+ body[:zone][:items] = records.map do |r|
351
+ r.to_h.transform_keys { |k| "rrset_#{k}" }
352
+ end
353
+ end
354
+
355
+ url_ = url
356
+ url_ += "?sharing_id=#{CGI.escape sharing_id}" if sharing_id
357
+
358
+ GandiV5.post url_, body.to_json
359
+ fetch(fqdn)
360
+ end
361
+
362
+ # Fetch the list of known record types (A, CNAME, etc.)
363
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-dns-rrtypes
364
+ # @return [Array<String>]
365
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
366
+ def self.record_types
367
+ GandiV5.get("#{BASE}livedns/dns/rrtypes").last
368
+ end
369
+
370
+ # Get the LiveDNS servers to use for a domain.
371
+ # @note Does not take into account any NS records that exist in the zone.
372
+ # @see https://api.gandi.net/docs/livedns/#get-v5-livedns-nameservers-fqdn
373
+ # @param fqdn [String, #to_s] the fully qualified domain to hash in
374
+ # in order to get the LiveDNS servers to use.
375
+ # @return [Array<String>]
376
+ # @raise [GandiV5::Error::GandiError] if Gandi returns an error.
377
+ def self.generic_name_servers(fqdn)
378
+ GandiV5.get("#{BASE}livedns/nameservers/#{CGI.escape fqdn}").last
379
+ end
380
+
70
381
  private
71
382
 
72
383
  def url
73
- "#{BASE}domains/#{CGI.escape(fqdn)}"
384
+ "#{BASE}livedns/domains/#{CGI.escape fqdn}"
74
385
  end
75
386
 
76
387
  def self.url(fqdn = nil)
77
- "#{BASE}domains" + (fqdn ? "/#{CGI.escape(fqdn)}" : '')
388
+ "#{BASE}livedns/domains" + (fqdn ? "/#{CGI.escape fqdn}" : '')
78
389
  end
79
390
  private_class_method :url
80
391
  end