google-cloud-dns 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Dns
19
+ VERSION = "0.20.0"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,768 @@
1
+ # Copyright 2015 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/dns/change"
17
+ require "google/cloud/dns/zone/transaction"
18
+ require "google/cloud/dns/zone/list"
19
+ require "google/cloud/dns/record"
20
+ require "google/cloud/dns/importer"
21
+ require "time"
22
+
23
+ module Google
24
+ module Cloud
25
+ module Dns
26
+ ##
27
+ # # DNS Zone
28
+ #
29
+ # The managed zone is the container for DNS records for the same DNS name
30
+ # suffix and has a set of name servers that accept and responds to
31
+ # queries. A project can have multiple managed zones, but they must each
32
+ # have a unique name.
33
+ #
34
+ # @example
35
+ # require "google/cloud"
36
+ #
37
+ # gcloud = Google::Cloud.new
38
+ # dns = gcloud.dns
39
+ # zone = dns.zone "example-com"
40
+ # zone.records.each do |record|
41
+ # puts record.name
42
+ # end
43
+ #
44
+ # @see https://cloud.google.com/dns/zones/ Managing Zones
45
+ #
46
+ class Zone
47
+ ##
48
+ # @private The Service object.
49
+ attr_accessor :service
50
+
51
+ ##
52
+ # @private The Google API Client object.
53
+ attr_accessor :gapi
54
+
55
+ ##
56
+ # @private Create an empty Zone object.
57
+ def initialize
58
+ @service = nil
59
+ @gapi = {}
60
+ end
61
+
62
+ ##
63
+ # Unique identifier for the resource; defined by the server.
64
+ #
65
+ def id
66
+ @gapi.id
67
+ end
68
+
69
+ ##
70
+ # User assigned name for this resource. Must be unique within the
71
+ # project. The name must be 1-32 characters long, must begin with a
72
+ # letter, end with a letter or digit, and only contain lowercase
73
+ # letters, digits or dashes.
74
+ #
75
+ def name
76
+ @gapi.name
77
+ end
78
+
79
+ ##
80
+ # The DNS name of this managed zone, for instance "example.com.".
81
+ #
82
+ def dns
83
+ @gapi.dns_name
84
+ end
85
+
86
+ ##
87
+ # A string of at most 1024 characters associated with this resource for
88
+ # the user's convenience. Has no effect on the managed zone's function.
89
+ #
90
+ def description
91
+ @gapi.description
92
+ end
93
+
94
+ ##
95
+ # Delegate your managed_zone to these virtual name servers; defined by
96
+ # the server.
97
+ #
98
+ def name_servers
99
+ Array(@gapi.name_servers)
100
+ end
101
+
102
+ ##
103
+ # Optionally specifies the NameServerSet for this ManagedZone. A
104
+ # NameServerSet is a set of DNS name servers that all host the same
105
+ # ManagedZones. Most users will leave this field unset.
106
+ #
107
+ def name_server_set
108
+ @gapi.name_server_set
109
+ end
110
+
111
+ ##
112
+ # The time that this resource was created on the server.
113
+ #
114
+ def created_at
115
+ Time.parse @gapi.creation_time
116
+ rescue
117
+ nil
118
+ end
119
+
120
+ ##
121
+ # Permanently deletes the zone.
122
+ #
123
+ # @param [Boolean] force If `true`, ensures the deletion of the zone by
124
+ # first deleting all records. If `false` and the zone contains
125
+ # non-essential records, the request will fail. Default is `false`.
126
+ #
127
+ # @return [Boolean] Returns `true` if the zone was deleted.
128
+ #
129
+ # @example
130
+ # require "google/cloud"
131
+ #
132
+ # gcloud = Google::Cloud.new
133
+ # dns = gcloud.dns
134
+ # zone = dns.zone "example-com"
135
+ # zone.delete
136
+ #
137
+ # @example The zone can be forcefully deleted with the `force` option:
138
+ # require "google/cloud"
139
+ #
140
+ # gcloud = Google::Cloud.new
141
+ # dns = gcloud.dns
142
+ # zone = dns.zone "example-com"
143
+ # zone.delete force: true
144
+ #
145
+ def delete force: false
146
+ clear! if force
147
+
148
+ ensure_service!
149
+ service.delete_zone id
150
+ true
151
+ end
152
+
153
+ ##
154
+ # Removes non-essential records from the zone. Only NS and SOA records
155
+ # will be kept.
156
+ #
157
+ # @example
158
+ # require "google/cloud"
159
+ #
160
+ # gcloud = Google::Cloud.new
161
+ # dns = gcloud.dns
162
+ # zone = dns.zone "example-com"
163
+ # zone.clear!
164
+ #
165
+ def clear!
166
+ non_essential = records.all.reject { |r| %w(SOA NS).include?(r.type) }
167
+ change = update [], non_essential
168
+ change.wait_until_done! unless change.nil?
169
+ end
170
+
171
+ ##
172
+ # Retrieves an existing change by id.
173
+ #
174
+ # @param [String] change_id The id of a change.
175
+ #
176
+ # @return [Google::Cloud::Dns::Change, nil] Returns `nil` if the change
177
+ # does not exist.
178
+ #
179
+ # @example
180
+ # require "google/cloud"
181
+ #
182
+ # gcloud = Google::Cloud.new
183
+ # dns = gcloud.dns
184
+ # zone = dns.zone "example-com"
185
+ # change = zone.change "2"
186
+ # if change
187
+ # puts "#{change.id} - #{change.started_at} - #{change.status}"
188
+ # end
189
+ #
190
+ def change change_id
191
+ ensure_service!
192
+ gapi = service.get_change id, change_id
193
+ Change.from_gapi gapi, self
194
+ rescue Google::Cloud::NotFoundError
195
+ nil
196
+ end
197
+ alias_method :find_change, :change
198
+ alias_method :get_change, :change
199
+
200
+ ##
201
+ # Retrieves the list of changes belonging to the zone.
202
+ #
203
+ # @param [String] token A previously-returned page token representing
204
+ # part of the larger set of results to view.
205
+ # @param [Integer] max Maximum number of changes to return.
206
+ # @param [Symbol, String] order Sort the changes by change sequence.
207
+ #
208
+ # Acceptable values are:
209
+ #
210
+ # * `asc` - Sort by ascending change sequence
211
+ # * `desc` - Sort by descending change sequence
212
+ #
213
+ # @return [Array<Google::Cloud::Dns::Change>] (See
214
+ # {Google::Cloud::Dns::Change::List})
215
+ #
216
+ # @example
217
+ # require "google/cloud"
218
+ #
219
+ # gcloud = Google::Cloud.new
220
+ # dns = gcloud.dns
221
+ # zone = dns.zone "example-com"
222
+ # changes = zone.changes
223
+ # changes.each do |change|
224
+ # puts "#{change.id} - #{change.started_at} - #{change.status}"
225
+ # end
226
+ #
227
+ # @example The changes can be sorted by change sequence:
228
+ # require "google/cloud"
229
+ #
230
+ # gcloud = Google::Cloud.new
231
+ # dns = gcloud.dns
232
+ # zone = dns.zone "example-com"
233
+ # changes = zone.changes order: :desc
234
+ #
235
+ # @example Retrieve all changes: (See {Change::List#all})
236
+ # require "google/cloud"
237
+ #
238
+ # gcloud = Google::Cloud.new
239
+ # dns = gcloud.dns
240
+ # zone = dns.zone "example-com"
241
+ # changes = zone.changes
242
+ # changes.all do |change|
243
+ # puts "#{change.name} - #{change.status}"
244
+ # end
245
+ #
246
+ def changes token: nil, max: nil, order: nil
247
+ ensure_service!
248
+ # Fix the sort options
249
+ order = adjust_change_sort_order order
250
+ sort = "changeSequence" if order
251
+ # Continue with the API call
252
+ gapi = service.list_changes id, token: token, max: max,
253
+ order: order, sort: sort
254
+ Change::List.from_gapi gapi, self, max, order
255
+ end
256
+ alias_method :find_changes, :changes
257
+
258
+ ##
259
+ # Retrieves the list of records belonging to the zone. Records can be
260
+ # filtered by name and type. The name argument can be a subdomain (e.g.,
261
+ # `www`) fragment for convenience, but notice that the retrieved
262
+ # record's domain name is always fully-qualified.
263
+ #
264
+ # @param [String] name Return only records with this domain or subdomain
265
+ # name.
266
+ # @param [String] type Return only records with this [record
267
+ # type](https://cloud.google.com/dns/what-is-cloud-dns). If present,
268
+ # the `name` parameter must also be present.
269
+ # @param [String] token A previously-returned page token representing
270
+ # part of the larger set of results to view.
271
+ # @param [Integer] max Maximum number of records to return.
272
+ #
273
+ # @return [Array<Google::Cloud::Dns::Record>] (See
274
+ # {Google::Cloud::Dns::Record::List})
275
+ #
276
+ # @example
277
+ # require "google/cloud"
278
+ #
279
+ # gcloud = Google::Cloud.new
280
+ # dns = gcloud.dns
281
+ # zone = dns.zone "example-com"
282
+ # records = zone.records
283
+ # records.each do |record|
284
+ # puts record.name
285
+ # end
286
+ #
287
+ # @example Records can be filtered by name and type:
288
+ # require "google/cloud"
289
+ #
290
+ # gcloud = Google::Cloud.new
291
+ # dns = gcloud.dns
292
+ # zone = dns.zone "example-com"
293
+ # records = zone.records "www", "A"
294
+ # records.first.name #=> "www.example.com."
295
+ #
296
+ # @example Retrieve all records:
297
+ # require "google/cloud"
298
+ #
299
+ # gcloud = Google::Cloud.new
300
+ # dns = gcloud.dns
301
+ # zone = dns.zone "example-com"
302
+ # records = zone.records "example.com."
303
+ #
304
+ # records.all do |record|
305
+ # puts record.name
306
+ # end
307
+ #
308
+ def records name = nil, type = nil, token: nil, max: nil
309
+ ensure_service!
310
+
311
+ name = fqdn(name) if name
312
+
313
+ gapi = service.list_records id, name, type, token: token, max: max
314
+ Record::List.from_gapi gapi, self, name, type, max
315
+ end
316
+ alias_method :find_records, :records
317
+
318
+ ##
319
+ # Creates a new, unsaved Record that can be added to a Zone.
320
+ #
321
+ # @return [Google::Cloud::Dns::Record]
322
+ #
323
+ # @example
324
+ # require "google/cloud"
325
+ #
326
+ # gcloud = Google::Cloud.new
327
+ # dns = gcloud.dns
328
+ # zone = dns.zone "example-com"
329
+ # record = zone.record "example.com.", "A", 86400, ["1.2.3.4"]
330
+ # zone.add record
331
+ #
332
+ def record name, type, ttl, data
333
+ Google::Cloud::Dns::Record.new fqdn(name), type, ttl, data
334
+ end
335
+ alias_method :new_record, :record
336
+
337
+ ##
338
+ # Exports the zone to a local [DNS zone
339
+ # file](https://en.wikipedia.org/wiki/Zone_file).
340
+ #
341
+ # @param [String] path The path on the local file system to write the
342
+ # data to. The path provided must be writable.
343
+ #
344
+ # @return [File] An object on the local file system.
345
+ #
346
+ # @example
347
+ # require "google/cloud"
348
+ #
349
+ # gcloud = Google::Cloud.new
350
+ # dns = gcloud.dns
351
+ # zone = dns.zone "example-com"
352
+ #
353
+ # zone.export "path/to/db.example.com"
354
+ #
355
+ def export path
356
+ File.open path, "w" do |f|
357
+ f.write to_zonefile
358
+ end
359
+ end
360
+
361
+ ##
362
+ # Imports resource records from a [DNS zone
363
+ # file](https://en.wikipedia.org/wiki/Zone_file), adding the new records
364
+ # to the zone, without removing any existing records from the zone.
365
+ #
366
+ # Because the Google Cloud DNS API only accepts a single resource record
367
+ # for each `name` and `type` combination (with multiple `data`
368
+ # elements), the zone file's records are merged as necessary. During
369
+ # this merge, the lowest `ttl` of the merged records is used. If none of
370
+ # the merged records have a `ttl` value, the zone file's global TTL is
371
+ # used for the record.
372
+ #
373
+ # The zone file's SOA and NS records are not imported, because the zone
374
+ # was given SOA and NS records when it was created. These generated
375
+ # records point to Cloud DNS name servers.
376
+ #
377
+ # This operation automatically updates the SOA record serial number
378
+ # unless prevented with the `skip_soa` option. See {#update} for
379
+ # details.
380
+ #
381
+ # The Google Cloud DNS service requires that record names and data use
382
+ # fully-qualified addresses. The @ symbol is not accepted, nor are
383
+ # unqualified subdomain addresses like www. If your zone file contains
384
+ # such values, you may need to pre-process it in order for the import
385
+ # operation to succeed.
386
+ #
387
+ # @param [String, IO] path_or_io The path to a zone file on the
388
+ # filesystem, or an IO instance from which zone file data can be read.
389
+ # @param [String, Array<String>] only Include only records of this type
390
+ # or types.
391
+ # @param [String, Array<String>] except Exclude records of this type or
392
+ # types.
393
+ # @param [Boolean] skip_soa Do not automatically update the SOA record
394
+ # serial number. See {#update} for details.
395
+ # @param [Integer, lambda, Proc] soa_serial A value (or a lambda or Proc
396
+ # returning a value) for the new SOA record serial number. See
397
+ # {#update} for details.
398
+ #
399
+ # @return [Google::Cloud::Dns::Change] A new change adding the imported
400
+ # Record instances.
401
+ #
402
+ # @example
403
+ # require "google/cloud"
404
+ #
405
+ # gcloud = Google::Cloud.new
406
+ # dns = gcloud.dns
407
+ # zone = dns.zone "example-com"
408
+ # change = zone.import "path/to/db.example.com"
409
+ #
410
+ def import path_or_io, only: nil, except: nil,
411
+ skip_soa: nil, soa_serial: nil
412
+ except = (Array(except).map(&:to_s).map(&:upcase) + %w(SOA NS)).uniq
413
+ importer = Google::Cloud::Dns::Importer.new self, path_or_io
414
+ additions = importer.records only: only, except: except
415
+ update additions, [], skip_soa: skip_soa, soa_serial: soa_serial
416
+ end
417
+
418
+ # rubocop:disable all
419
+ # Disabled rubocop because this complexity cannot easily be avoided.
420
+
421
+ ##
422
+ # Adds and removes Records from the zone. All changes are made in a
423
+ # single API request.
424
+ #
425
+ # The best way to add, remove, and update multiple records in a single
426
+ # [transaction](https://cloud.google.com/dns/records) is with a block.
427
+ # See {Zone::Transaction}.
428
+ #
429
+ # If the SOA record for the zone is not present in `additions` or
430
+ # `deletions` (and if present in one, it should be present in the
431
+ # other), it will be added to both, and its serial number will be
432
+ # incremented by adding `1`. This update to the SOA record can be
433
+ # prevented with the `skip_soa` option. To provide your own value or
434
+ # behavior for the new serial number, use the `soa_serial` option.
435
+ #
436
+ # @param [Record, Array<Record>] additions The Record or array of
437
+ # records to add.
438
+ # @param [Record, Array<Record>] deletions The Record or array of
439
+ # records to remove.
440
+ # @param [Boolean] skip_soa Do not automatically update the SOA record
441
+ # serial number.
442
+ # @param [Integer, lambda, Proc] soa_serial A value (or a lambda or Proc
443
+ # returning a value) for the new SOA record serial number.
444
+ # @yield [tx] a block yielding a new transaction
445
+ # @yieldparam [Zone::Transaction] tx the transaction object
446
+ #
447
+ # @return [Google::Cloud::Dns::Change]
448
+ #
449
+ # @example Using a block:
450
+ # require "google/cloud"
451
+ #
452
+ # gcloud = Google::Cloud.new
453
+ # dns = gcloud.dns
454
+ # zone = dns.zone "example-com"
455
+ # change = zone.update do |tx|
456
+ # tx.add "example.com.", "A", 86400, "1.2.3.4"
457
+ # tx.remove "example.com.", "TXT"
458
+ # tx.replace "example.com.", "MX", 86400, ["10 mail1.example.com.",
459
+ # "20 mail2.example.com."]
460
+ # tx.modify "www.example.com.", "CNAME" do |cname|
461
+ # cname.ttl = 86400 # only change the TTL
462
+ # end
463
+ # end
464
+ #
465
+ # @example Or you can provide the record objects to add and remove:
466
+ # require "google/cloud"
467
+ #
468
+ # gcloud = Google::Cloud.new
469
+ # dns = gcloud.dns
470
+ # zone = dns.zone "example-com"
471
+ # new_record = zone.record "example.com.", "A", 86400, ["1.2.3.4"]
472
+ # old_record = zone.record "example.com.", "A", 18600, ["1.2.3.4"]
473
+ # change = zone.update [new_record], [old_record]
474
+ #
475
+ # @example Using a lambda or Proc to update current SOA serial number:
476
+ # require "google/cloud"
477
+ #
478
+ # gcloud = Google::Cloud.new
479
+ # dns = gcloud.dns
480
+ # zone = dns.zone "example-com"
481
+ # new_record = zone.record "example.com.", "A", 86400, ["1.2.3.4"]
482
+ # change = zone.update new_record, soa_serial: lambda { |sn| sn + 10 }
483
+ #
484
+ def update additions = [], deletions = [], skip_soa: nil, soa_serial: nil
485
+ # Handle only sending in options
486
+ if additions.is_a?(::Hash) && deletions.empty? && options.empty?
487
+ options = additions
488
+ additions = []
489
+ elsif deletions.is_a?(::Hash) && options.empty?
490
+ options = deletions
491
+ deletions = []
492
+ end
493
+
494
+ additions = Array additions
495
+ deletions = Array deletions
496
+
497
+ if block_given?
498
+ updater = Zone::Transaction.new self
499
+ yield updater
500
+ additions += updater.additions
501
+ deletions += updater.deletions
502
+ end
503
+
504
+ to_add = additions - deletions
505
+ to_remove = deletions - additions
506
+ return nil if to_add.empty? && to_remove.empty?
507
+ unless skip_soa || detect_soa(to_add) || detect_soa(to_remove)
508
+ increment_soa to_add, to_remove, soa_serial
509
+ end
510
+ create_change to_add, to_remove
511
+ end
512
+
513
+ # rubocop:enable all
514
+
515
+ ##
516
+ # Adds a record to the Zone. In order to update existing records, or add
517
+ # and delete records in the same transaction, use #update.
518
+ #
519
+ # This operation automatically updates the SOA record serial number
520
+ # unless prevented with the `skip_soa` option. See {#update} for
521
+ # details.
522
+ #
523
+ # @param [String] name The owner of the record. For example:
524
+ # `example.com.`.
525
+ # @param [String] type The identifier of a [supported record
526
+ # type](https://cloud.google.com/dns/what-is-cloud-dns).
527
+ # For example: `A`, `AAAA`, `CNAME`, `MX`, or `TXT`.
528
+ # @param [Integer] ttl The number of seconds that the record can be
529
+ # cached by resolvers.
530
+ # @param [String, Array<String>] data The resource record data, as
531
+ # determined by `type` and defined in [RFC
532
+ # 1035 (section 5)](http://tools.ietf.org/html/rfc1035#section-5) and
533
+ # [RFC 1034
534
+ # (section 3.6.1)](http://tools.ietf.org/html/rfc1034#section-3.6.1).
535
+ # For example: `192.0.2.1` or `example.com.`.
536
+ # @param [Boolean] skip_soa Do not automatically update the SOA record
537
+ # serial number. See {#update} for details.
538
+ # @param [Integer+, lambda, Proc] soa_serial A value (or a lambda or
539
+ # Proc returning a value) for the new SOA record serial number. See
540
+ # {#update} for details.
541
+ #
542
+ # @return [Google::Cloud::Dns::Change]
543
+ #
544
+ # @example
545
+ # require "google/cloud"
546
+ #
547
+ # gcloud = Google::Cloud.new
548
+ # dns = gcloud.dns
549
+ # zone = dns.zone "example-com"
550
+ # change = zone.add "example.com.", "A", 86400, ["1.2.3.4"]
551
+ #
552
+ def add name, type, ttl, data, skip_soa: nil, soa_serial: nil
553
+ update [record(name, type, ttl, data)], [],
554
+ skip_soa: skip_soa, soa_serial: soa_serial
555
+ end
556
+
557
+ ##
558
+ # Removes records from the Zone. The records are looked up before they
559
+ # are removed. In order to update existing records, or add and remove
560
+ # records in the same transaction, use #update.
561
+ #
562
+ # This operation automatically updates the SOA record serial number
563
+ # unless prevented with the `skip_soa` option. See {#update} for
564
+ # details.
565
+ #
566
+ # @param [String] name The owner of the record. For example:
567
+ # `example.com.`.
568
+ # @param [String] type The identifier of a [supported record
569
+ # type](https://cloud.google.com/dns/what-is-cloud-dns).
570
+ # For example: `A`, `AAAA`, `CNAME`, `MX`, or `TXT`.
571
+ # @param [Boolean] skip_soa Do not automatically update the SOA record
572
+ # serial number. See {#update} for details.
573
+ # @param [Integer+, lambda, Proc] soa_serial A value (or a lambda or
574
+ # Proc returning a value) for the new SOA record serial number. See
575
+ # {#update} for details.
576
+ #
577
+ # @return [Google::Cloud::Dns::Change]
578
+ #
579
+ # @example
580
+ # require "google/cloud"
581
+ #
582
+ # gcloud = Google::Cloud.new
583
+ # dns = gcloud.dns
584
+ # zone = dns.zone "example-com"
585
+ # change = zone.remove "example.com.", "A"
586
+ #
587
+ def remove name, type, skip_soa: nil, soa_serial: nil
588
+ update [], records(name, type).all.to_a,
589
+ skip_soa: skip_soa, soa_serial: soa_serial
590
+ end
591
+
592
+ ##
593
+ # Replaces existing records on the Zone. Records matching the `name` and
594
+ # `type` are replaced. In order to update existing records, or add and
595
+ # delete records in the same transaction, use #update.
596
+ #
597
+ # This operation automatically updates the SOA record serial number
598
+ # unless prevented with the `skip_soa` option. See {#update} for
599
+ # details.
600
+ #
601
+ # @param [String] name The owner of the record. For example:
602
+ # `example.com.`.
603
+ # @param [String] type The identifier of a [supported record
604
+ # type](https://cloud.google.com/dns/what-is-cloud-dns).
605
+ # For example: `A`, `AAAA`, `CNAME`, `MX`, or `TXT`.
606
+ # @param [Integer] ttl The number of seconds that the record can be
607
+ # cached by resolvers.
608
+ # @param [String, Array<String>] data The resource record data, as
609
+ # determined by `type` and defined in [RFC 1035 (section
610
+ # 5)](http://tools.ietf.org/html/rfc1035#section-5) and [RFC 1034
611
+ # (section 3.6.1)](http://tools.ietf.org/html/rfc1034#section-3.6.1).
612
+ # For example: `192.0.2.1` or `example.com.`.
613
+ # @param [Boolean] skip_soa Do not automatically update the SOA record
614
+ # serial number. See {#update} for details.
615
+ # @param [Integer+, lambda, Proc] soa_serial A value (or a lambda or
616
+ # Proc returning a value) for the new SOA record serial number. See
617
+ # {#update} for details.
618
+ #
619
+ # @return [Google::Cloud::Dns::Change]
620
+ #
621
+ # @example
622
+ # require "google/cloud"
623
+ #
624
+ # gcloud = Google::Cloud.new
625
+ # dns = gcloud.dns
626
+ # zone = dns.zone "example-com"
627
+ # change = zone.replace "example.com.", "A", 86400, ["5.6.7.8"]
628
+ #
629
+ def replace name, type, ttl, data, skip_soa: nil, soa_serial: nil
630
+ update [record(name, type, ttl, data)],
631
+ records(name, type).all.to_a,
632
+ skip_soa: skip_soa, soa_serial: soa_serial
633
+ end
634
+
635
+ # @private
636
+ def to_zonefile
637
+ records.all.map(&:to_zonefile_records).flatten.join("\n")
638
+ end
639
+
640
+ ##
641
+ # Modifies records on the Zone. Records matching the `name` and `type`
642
+ # are yielded to the block where they can be modified.
643
+ #
644
+ # This operation automatically updates the SOA record serial number
645
+ # unless prevented with the `skip_soa` option. See {#update} for
646
+ # details.
647
+ #
648
+ # @param [String] name The owner of the record. For example:
649
+ # `example.com.`.
650
+ # @param [String] type The identifier of a [supported record
651
+ # type](https://cloud.google.com/dns/what-is-cloud-dns).
652
+ # For example: `A`, `AAAA`, `CNAME`, `MX`, or `TXT`.
653
+ # @param [Boolean] skip_soa Do not automatically update the SOA record
654
+ # serial number. See {#update} for details.
655
+ # @param [Integer+, lambda, Proc] soa_serial A value (or a lambda or
656
+ # Proc returning a value) for the new SOA record serial number. See
657
+ # {#update} for details.
658
+ # @yield [record] a block yielding each matching record
659
+ # @yieldparam [Record] record the record to be modified
660
+ #
661
+ # @return [Google::Cloud::Dns::Change]
662
+ #
663
+ # @example
664
+ # require "google/cloud"
665
+ #
666
+ # gcloud = Google::Cloud.new
667
+ # dns = gcloud.dns
668
+ # zone = dns.zone "example-com"
669
+ # change = zone.modify "example.com.", "MX" do |mx|
670
+ # mx.ttl = 3600 # change only the TTL
671
+ # end
672
+ #
673
+ def modify name, type, skip_soa: nil, soa_serial: nil
674
+ existing = records(name, type).all.to_a
675
+ updated = existing.map(&:dup)
676
+ updated.each { |r| yield r }
677
+ update updated, existing, skip_soa: skip_soa, soa_serial: soa_serial
678
+ end
679
+
680
+ ##
681
+ # This helper converts the given domain name or subdomain (e.g., `www`)
682
+ # fragment to a [fully qualified domain name
683
+ # (FQDN)](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) for
684
+ # the zone's #dns. If the argument is already a FQDN, it is returned
685
+ # unchanged.
686
+ #
687
+ # @param [String] domain_name The name to convert to a fully qualified
688
+ # domain name.
689
+ #
690
+ # @return [String] A fully qualified domain name.
691
+ #
692
+ # @example
693
+ # require "google/cloud"
694
+ #
695
+ # gcloud = Google::Cloud.new
696
+ # dns = gcloud.dns
697
+ # zone = dns.zone "example-com"
698
+ # zone.fqdn "www" #=> "www.example.com."
699
+ # zone.fqdn "@" #=> "example.com."
700
+ # zone.fqdn "mail.example.com." #=> "mail.example.com."
701
+ #
702
+ def fqdn domain_name
703
+ Service.fqdn domain_name, dns
704
+ end
705
+
706
+ ##
707
+ # @private New Zone from a Google API Client object.
708
+ def self.from_gapi gapi, conn
709
+ new.tap do |f|
710
+ f.gapi = gapi
711
+ f.service = conn
712
+ end
713
+ end
714
+
715
+ protected
716
+
717
+ ##
718
+ # Raise an error unless an active connection is available.
719
+ def ensure_service!
720
+ fail "Must have active connection" unless service
721
+ end
722
+
723
+ def create_change additions, deletions
724
+ ensure_service!
725
+ gapi = service.create_change id, additions.map(&:to_gapi),
726
+ deletions.map(&:to_gapi)
727
+ Change.from_gapi gapi, self
728
+ end
729
+
730
+ def increment_soa to_add, to_remove, soa_serial
731
+ current_soa = detect_soa records(dns, "SOA").all
732
+ return false if current_soa.nil?
733
+ updated_soa = current_soa.dup
734
+ updated_soa.data[0] = replace_soa_serial updated_soa.data[0],
735
+ soa_serial
736
+ to_add << updated_soa
737
+ to_remove << current_soa
738
+ end
739
+
740
+ def detect_soa records
741
+ records.detect { |r| r.type == "SOA" }
742
+ end
743
+
744
+ def replace_soa_serial soa_data, soa_serial
745
+ soa_data = soa_data.split " "
746
+ current_serial = soa_data[2].to_i
747
+ soa_data[2] = if soa_serial && soa_serial.respond_to?(:call)
748
+ soa_serial.call current_serial
749
+ elsif soa_serial
750
+ soa_serial.to_i
751
+ else
752
+ current_serial + 1
753
+ end
754
+ soa_data.join " "
755
+ end
756
+
757
+ def adjust_change_sort_order order
758
+ return nil if order.nil?
759
+ if order.to_s.downcase.start_with? "d"
760
+ "descending"
761
+ else
762
+ "ascending"
763
+ end
764
+ end
765
+ end
766
+ end
767
+ end
768
+ end