zonesync 0.14.3 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46a1af2deec25de218acd4d6cebf63f1a0e6d996d95ec4a0311603e943395501
4
- data.tar.gz: 7b6b8e32fb8709a02299fa1a805facf2c1dec6cf3491010f960fed90516c31b9
3
+ metadata.gz: 413d039b491283ed46fa0b03a08a5bbd8c02489798850119b27372f8017ea30e
4
+ data.tar.gz: 2ffb50fce0dc89cbf955d5528e280a167b5170bc994d17ed7d02495a9b53e337
5
5
  SHA512:
6
- metadata.gz: 90730972e5edb267dafb7c4f7f3da11fbccf5befd25c0d1b99817804275a13543bd1f67b55d4945a982ece2790da74341293e394af120ebce2a8e64b79c77356
7
- data.tar.gz: da7f4c54f907de870e88b8eeb8c33efa51e8dfe46206a31b366c90d6c6842b4d22e9fba426fb94fe4cea9347415acc692234dc86366c6a6d7cdbbfc2333f7283
6
+ metadata.gz: '09fe6fd394d6ae175c9c0a5325206b3fc0c5fef6523fb8802a14d6e4b90d95d5282fb58de35bc6fceac4642df87021ac352b50368a2308fc0db8c7b0e190f350'
7
+ data.tar.gz: 1cb14bf843af73c34dd1c49205438e2e62e4d8e5f810932ff410dce2e5d7ab36768ef2c429852ce7f1b858958ab958839db0a0cdabc4bb707d54ceae1f3ff02b
@@ -23,6 +23,13 @@ module Zonesync
23
23
  operations
24
24
  end
25
25
 
26
+ def apply(operations)
27
+ operations.each do |method, records|
28
+ yield method, records
29
+ send(method, *records)
30
+ end
31
+ end
32
+
26
33
  def diff(other)
27
34
  Diff.new(
28
35
  from: diffable_records,
@@ -56,6 +56,11 @@ module Zonesync
56
56
  Record.single_record_per_name?(type)
57
57
  end
58
58
 
59
+ # A CNAME cannot coexist with any other data at its owner name.
60
+ def excludes?(other)
61
+ name == other.name && type != other.type && [type, other.type].include?("CNAME")
62
+ end
63
+
59
64
  def self.single_record_per_name?(type)
60
65
  type == "CNAME" || type == "SOA"
61
66
  end
@@ -33,40 +33,24 @@ module Zonesync
33
33
  *(remaining_records.any? ? [[remaining_records, "CREATE"]] : [])
34
34
  ]
35
35
 
36
- http.post("", ERB.new(<<~XML, trim_mode: "-").result(binding))
37
- <?xml version="1.0" encoding="UTF-8"?>
38
- <ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
39
- <ChangeBatch>
40
- <Changes>
41
- <%- grouped.each do |records_list, action| -%>
42
- <%- records_grouped = records_list.group_by { |r| [r.name, r.type, r.ttl] } -%>
43
- <%- records_grouped.each do |(name, type, ttl), group_records| -%>
44
- <Change>
45
- <Action><%= action %></Action>
46
- <ResourceRecordSet>
47
- <Name><%= name %></Name>
48
- <Type><%= type %></Type>
49
- <TTL><%= ttl %></TTL>
50
- <ResourceRecords>
51
- <%- group_records.each do |group_record| -%>
52
- <ResourceRecord>
53
- <Value><%= rdata_for_api(group_record) %></Value>
54
- </ResourceRecord>
55
- <%- end -%>
56
- </ResourceRecords>
57
- </ResourceRecordSet>
58
- </Change>
59
- <%- end -%>
60
- <%- end -%>
61
- </Changes>
62
- </ChangeBatch>
63
- </ChangeResourceRecordSetsRequest>
64
- XML
36
+ post_changes(grouped)
65
37
 
66
38
  invalidate_cache!
67
39
  end
68
40
  end
69
41
 
42
+ def apply(operations, &block)
43
+ names = type_change_names(operations)
44
+ batched, rest = operations.partition do |method, (record)|
45
+ method != :change && names.include?(record.name)
46
+ end
47
+ batched.group_by { |_, (record)| record.name }.each do |name, ops|
48
+ ops.each(&block)
49
+ replace_at_name(name, ops)
50
+ end
51
+ super(rest, &block)
52
+ end
53
+
70
54
  def change(old_record, new_record)
71
55
  new_set = if old_record.manifest? || old_record.checksum?
72
56
  # our metadata records are singletons, always replace
@@ -111,6 +95,61 @@ module Zonesync
111
95
  @zonefile = nil
112
96
  end
113
97
 
98
+ def type_change_names(operations)
99
+ removed = operations.filter_map { |method, (record)| record if method == :remove }
100
+ added = operations.filter_map { |method, (record)| record if method == :add }
101
+ added.select { |a| removed.any? { |r| r.excludes?(a) } }.map(&:name).uniq
102
+ end
103
+
104
+ def replace_at_name(name, ops)
105
+ removed = ops.filter_map { |method, (record)| record if method == :remove }
106
+ added = ops.filter_map { |method, (record)| record if method == :add }
107
+
108
+ deletes, creates = [], []
109
+ (removed + added).map(&:type).uniq.each do |type|
110
+ before = records.select { |r| r.name == name && r.type == type }
111
+ after = before.reject { |r| removed.any? { |x| x.identical_to?(r) } }
112
+ after += added.select { |r| r.type == type && after.none? { |x| x.identical_to?(r) } }
113
+ deletes.concat(before)
114
+ creates.concat(after)
115
+ end
116
+
117
+ post_changes([[deletes, "DELETE"], [creates, "CREATE"]])
118
+ invalidate_cache!
119
+ end
120
+
121
+ def post_changes(grouped)
122
+ http.post("", ERB.new(<<~XML, trim_mode: "-").result(binding))
123
+ <?xml version="1.0" encoding="UTF-8"?>
124
+ <ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
125
+ <ChangeBatch>
126
+ <Changes>
127
+ <%- grouped.each do |records_list, action| -%>
128
+ <%- records_grouped = records_list.group_by { |r| [r.name, r.type, r.ttl] } -%>
129
+ <%- records_grouped.each do |(name, type, ttl), group_records| -%>
130
+ <Change>
131
+ <Action><%= action %></Action>
132
+ <ResourceRecordSet>
133
+ <Name><%= name %></Name>
134
+ <Type><%= type %></Type>
135
+ <TTL><%= ttl %></TTL>
136
+ <ResourceRecords>
137
+ <%- group_records.each do |group_record| -%>
138
+ <ResourceRecord>
139
+ <Value><%= rdata_for_api(group_record) %></Value>
140
+ </ResourceRecord>
141
+ <%- end -%>
142
+ </ResourceRecords>
143
+ </ResourceRecordSet>
144
+ </Change>
145
+ <%- end -%>
146
+ <%- end -%>
147
+ </Changes>
148
+ </ChangeBatch>
149
+ </ChangeResourceRecordSetsRequest>
150
+ XML
151
+ end
152
+
114
153
  def change_record(action, record)
115
154
  http.post("", <<~XML)
116
155
  <?xml version="1.0" encoding="UTF-8"?>
data/lib/zonesync/sync.rb CHANGED
@@ -38,9 +38,10 @@ module Zonesync
38
38
  end
39
39
  end
40
40
 
41
- operations.each do |method, records|
42
- Logger.log(method, records, dry_run: dry_run)
43
- destination.send(method, *records) unless dry_run
41
+ if dry_run
42
+ operations.each { |method, records| Logger.log(method, records, dry_run: true) }
43
+ else
44
+ destination.apply(operations) { |method, records| Logger.log(method, records) }
44
45
  end
45
46
  end
46
47
  end
@@ -89,6 +89,8 @@ module Zonesync
89
89
  end
90
90
 
91
91
  def validate_addition(record, force: false)
92
+ excluded_record = find_excluded_record(record)
93
+ return [excluded_record, record] if excluded_record
92
94
  return nil if manifest.matches?(record)
93
95
  return nil if force
94
96
 
@@ -106,6 +108,13 @@ module Zonesync
106
108
  [conflicting_record, record]
107
109
  end
108
110
 
111
+ def find_excluded_record(record)
112
+ removed = operations.filter_map { |method, (r)| r if method == :remove }
113
+ destination.records.find do |r|
114
+ r.excludes?(record) && removed.none? { |x| x.identical_to?(r) }
115
+ end
116
+ end
117
+
109
118
  def find_v2_conflict(record, expected_hashes)
110
119
  destination.records.find do |r|
111
120
  next if r.manifest? || r.checksum?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zonesync
4
- VERSION = "0.14.3"
4
+ VERSION = "0.14.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zonesync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.3
4
+ version: 0.14.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2026-06-01 00:00:00.000000000 Z
12
+ date: 2026-09-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor