sprinkle_dns 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +76 -0
  7. data/LICENSE +21 -0
  8. data/README.md +181 -0
  9. data/Rakefile +1 -0
  10. data/examples/example01.rb +47 -0
  11. data/examples/example02.rb +61 -0
  12. data/examples/example03.rb +27 -0
  13. data/examples/example04.rb +22 -0
  14. data/lib/sprinkle_dns/cli/hosted_zone_diff.rb +206 -0
  15. data/lib/sprinkle_dns/cli/interactive_change_request_printer.rb +32 -0
  16. data/lib/sprinkle_dns/cli/propagated_change_request_printer.rb +33 -0
  17. data/lib/sprinkle_dns/client.rb +169 -0
  18. data/lib/sprinkle_dns/config.rb +43 -0
  19. data/lib/sprinkle_dns/core_ext/array_wrap.rb +11 -0
  20. data/lib/sprinkle_dns/core_ext/zonify.rb +4 -0
  21. data/lib/sprinkle_dns/entry_policy_service.rb +100 -0
  22. data/lib/sprinkle_dns/exceptions.rb +9 -0
  23. data/lib/sprinkle_dns/hosted_zone.rb +36 -0
  24. data/lib/sprinkle_dns/hosted_zone_alias.rb +91 -0
  25. data/lib/sprinkle_dns/hosted_zone_domain.rb +18 -0
  26. data/lib/sprinkle_dns/hosted_zone_entry.rb +97 -0
  27. data/lib/sprinkle_dns/providers/mock_client.rb +60 -0
  28. data/lib/sprinkle_dns/providers/route53_client.rb +155 -0
  29. data/lib/sprinkle_dns/version.rb +3 -0
  30. data/lib/sprinkle_dns.rb +5 -0
  31. data/logos/SDNS.png +0 -0
  32. data/logos/SDNS.svg +1 -0
  33. data/readme_files/delete_true_and_diff.png +0 -0
  34. data/readme_files/dry_run_and_diff.png +0 -0
  35. data/readme_files/force_false.png +0 -0
  36. data/spec/spec_helper.rb +110 -0
  37. data/spec/support/entry_helpers.rb +18 -0
  38. data/spec/unit/cli_hosted_zone_diff_spec.rb +30 -0
  39. data/spec/unit/hosted_zone_domain_spec.rb +12 -0
  40. data/spec/unit/hosted_zone_spec.rb +343 -0
  41. data/spec/unit/mock_client_spec.rb +59 -0
  42. data/spec/unit/sprinkle_dns_spec.rb +235 -0
  43. data/sprinkle_dns.gemspec +29 -0
  44. data/test_perms.rb.example +2 -0
  45. metadata +192 -0
@@ -0,0 +1,32 @@
1
+ module SprinkleDNS::CLI
2
+ class InteractiveChangeRequestPrinter
3
+ def initialize
4
+ reset!
5
+ end
6
+
7
+ def reset!
8
+ @redraws = 0
9
+ end
10
+
11
+ def draw(change_requests, sync_word = "SYNCING", synced_word = "SYNCED")
12
+ lines = []
13
+
14
+ change_requests.each do |change_request|
15
+ dots = '.' * change_request.tries
16
+ sync = change_request.in_sync ? '✔' : '✘'
17
+ status = change_request.in_sync ? synced_word : sync_word
18
+ lines << "#{sync} #{status} #{change_request.hosted_zone.name}#{dots}"
19
+ end
20
+
21
+ if @redraws > 0
22
+ clear = "\r" + ("\e[A\e[K") * lines.size
23
+ puts clear + lines.join("\n")
24
+ else
25
+ puts lines.join("\n")
26
+ end
27
+
28
+ @redraws += 1
29
+ sleep(2)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ module SprinkleDNS::CLI
2
+ class PropagatedChangeRequestPrinter
3
+ def initialize
4
+ reset!
5
+ end
6
+
7
+ def reset!
8
+ @completed = []
9
+ @printed = []
10
+ end
11
+
12
+ def draw(change_requests, sync_word: "SYNCING", synced_word: "SYNCED")
13
+ change_requests.each do |change_request|
14
+ if change_request.in_sync
15
+ hosted_zone_name = change_request.hosted_zone.name
16
+
17
+ unless @completed.include?(hosted_zone_name)
18
+ @completed << hosted_zone_name + '.' * change_request.tries
19
+ end
20
+ end
21
+ end
22
+
23
+ @completed.each do |complete|
24
+ unless @printed.include?(complete)
25
+ puts "#{complete}.. #{synced_word}!"
26
+ @printed << complete
27
+ end
28
+ end
29
+
30
+ sleep(2)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,169 @@
1
+ require 'sprinkle_dns/exceptions'
2
+ require 'sprinkle_dns/config'
3
+ require 'sprinkle_dns/hosted_zone'
4
+ require 'sprinkle_dns/hosted_zone_domain'
5
+ require 'sprinkle_dns/hosted_zone_entry'
6
+ require 'sprinkle_dns/hosted_zone_alias'
7
+ require 'sprinkle_dns/entry_policy_service'
8
+
9
+ require 'sprinkle_dns/cli/hosted_zone_diff'
10
+ require 'sprinkle_dns/cli/interactive_change_request_printer'
11
+ require 'sprinkle_dns/cli/propagated_change_request_printer'
12
+
13
+ require 'sprinkle_dns/core_ext/array_wrap'
14
+ require 'sprinkle_dns/core_ext/zonify'
15
+
16
+ module SprinkleDNS
17
+ class Client
18
+ attr_reader :wanted_hosted_zones, :config
19
+
20
+ def initialize(dns_provider, dry_run: false, diff: true, force: true, delete: false, interactive_progress: true, create_hosted_zones: false)
21
+ @config = SprinkleDNS::Config.new(
22
+ dry_run: dry_run,
23
+ diff: diff,
24
+ force: force,
25
+ delete: delete,
26
+ interactive_progress: interactive_progress,
27
+ create_hosted_zones: create_hosted_zones,
28
+ )
29
+ @dns_provider = dns_provider
30
+ @wanted_hosted_zones = []
31
+
32
+ @progress_printer = if @config.interactive_progress?
33
+ SprinkleDNS::CLI::InteractiveChangeRequestPrinter.new
34
+ else
35
+ SprinkleDNS::CLI::PropagatedChangeRequestPrinter.new
36
+ end
37
+ end
38
+
39
+ def entry(type, name, value, ttl = 3600, hosted_zone = nil)
40
+ hosted_zone = find_or_init_hosted_zone(name, hosted_zone)
41
+ name = zonify!(name)
42
+
43
+ if ['CNAME', 'MX'].include?(type)
44
+ value = Array.wrap(value)
45
+ value.map!{|v| zonify!(v)}
46
+ end
47
+ hosted_zone.add_or_update_hosted_zone_entry(HostedZoneEntry.new(type, name, Array.wrap(value), ttl, hosted_zone.name))
48
+ end
49
+
50
+ def alias(type, name, hosted_zone_id, dns_name, hosted_zone = nil)
51
+ hosted_zone = find_or_init_hosted_zone(name, hosted_zone)
52
+ name = zonify!(name)
53
+ dns_name = zonify!(dns_name)
54
+
55
+ hosted_zone.add_or_update_hosted_zone_entry(HostedZoneAlias.new(type, name, hosted_zone_id, dns_name, hosted_zone.name))
56
+ end
57
+
58
+ def compare
59
+ existing_hosted_zones = @dns_provider.fetch_hosted_zones(filter: @wanted_hosted_zones.map(&:name))
60
+
61
+ # Tell our existing hosted zones about our wanted changes
62
+ existing_hosted_zones.each do |existing_hosted_zone|
63
+ wanted_hosted_zone = @wanted_hosted_zones.find{|whz| whz.name == existing_hosted_zone.name}
64
+
65
+ wanted_hosted_zone.resource_record_sets.each do |entry|
66
+ existing_hosted_zone.add_or_update_hosted_zone_entry(entry)
67
+ end
68
+ end
69
+
70
+ [@wanted_hosted_zones, existing_hosted_zones]
71
+ end
72
+
73
+ def sprinkle!
74
+ wanted_hosted_zones, existing_hosted_zones = compare
75
+
76
+ missing_hosted_zone_names = wanted_hosted_zones.map(&:name) - existing_hosted_zones.map(&:name)
77
+ missing_hosted_zones = wanted_hosted_zones.select{|whz| missing_hosted_zone_names.include?(whz.name)}
78
+
79
+ if missing_hosted_zones.any? && !@config.create_hosted_zones?
80
+ missing_hosted_zones_error(missing_hosted_zones)
81
+ end
82
+
83
+ if @config.diff?
84
+ SprinkleDNS::CLI::HostedZoneDiff.new.diff(existing_hosted_zones, missing_hosted_zones, @config).each do |line|
85
+ puts line.join(' ')
86
+ end
87
+ end
88
+
89
+ if @config.dry_run?
90
+ return [existing_hosted_zones, nil]
91
+ end
92
+
93
+ hosted_zones = (existing_hosted_zones + missing_hosted_zones)
94
+
95
+ unless @config.force?
96
+ changes = hosted_zones.map{|h| SprinkleDNS::EntryPolicyService.new(h, @config)}.collect{|eps| eps.entries_to_change}.sum
97
+
98
+ messages = []
99
+
100
+ messages << "#{missing_hosted_zones.size} hosted-zone(s) to create" if missing_hosted_zones.any?
101
+ messages << "#{changes} change(s) to make" if changes > 0
102
+ print messages.join(' and ').concat(". Continue? (y/N)")
103
+
104
+ case gets.strip
105
+ when 'y', 'Y'
106
+ # continue
107
+ else
108
+ puts ".. exiting!"
109
+ return [hosted_zones, nil]
110
+ end
111
+ end
112
+
113
+ # Create missing hosted zones
114
+ change_requests_hosted_zones = @dns_provider.create_hosted_zones(missing_hosted_zones)
115
+ if change_requests_hosted_zones.any?
116
+ puts
117
+ puts "Creating hosted zones:"
118
+ @progress_printer.reset!
119
+ begin
120
+ @dns_provider.check_change_requests(change_requests_hosted_zones)
121
+ @progress_printer.draw(change_requests_hosted_zones, 'CREATING', 'CREATED')
122
+ end until change_requests_hosted_zones.all?{|cr| cr.in_sync}
123
+ end
124
+
125
+ # Update hosted zones
126
+ change_requests_entries = @dns_provider.change_hosted_zones(hosted_zones, @config)
127
+ if change_requests_entries.any?
128
+ puts
129
+ puts "Updating hosted zones:"
130
+ @progress_printer.reset!
131
+ begin
132
+ @dns_provider.check_change_requests(change_requests_entries)
133
+ @progress_printer.draw(change_requests_entries, 'UPDATING', 'UPDATED')
134
+ end until change_requests_entries.all?{|cr| cr.in_sync}
135
+ end
136
+
137
+ change_requests = change_requests_hosted_zones + change_requests_entries
138
+
139
+ [hosted_zones, change_requests]
140
+ end
141
+
142
+ private
143
+
144
+ def find_or_init_hosted_zone(record_name, hosted_zone_name)
145
+ hosted_zone_name ||= HostedZoneDomain::parse(record_name)
146
+ hosted_zone_name = zonify!(hosted_zone_name)
147
+
148
+ wanted_hosted_zone = @wanted_hosted_zones.find{|zone| zone.name == hosted_zone_name}
149
+ if wanted_hosted_zone.nil?
150
+ wanted_hosted_zone = HostedZone.new(hosted_zone_name)
151
+ @wanted_hosted_zones << wanted_hosted_zone
152
+ end
153
+
154
+ wanted_hosted_zone
155
+ end
156
+
157
+ def missing_hosted_zones_error(missing_hosted_zones)
158
+ error_message = []
159
+ error_message << "There are #{missing_hosted_zones.size} missing hosted zones:"
160
+
161
+ missing_hosted_zones.map(&:name).sort.each do |whz|
162
+ error_message << "- #{whz}"
163
+ end
164
+
165
+ raise error_message.join("\n")
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,43 @@
1
+ module SprinkleDNS
2
+ class Config
3
+ def initialize(dry_run: true, diff: true, force: false, delete: false, interactive_progress: true, create_hosted_zones: false)
4
+ @dry_run = dry_run
5
+ @diff = diff
6
+ @force = force
7
+ @delete = delete
8
+ @interactive_progress = interactive_progress
9
+ @create_hosted_zones = create_hosted_zones
10
+
11
+ raise SettingNotBoolean.new('dry_run is not a boolean') unless [true, false].include?(dry_run)
12
+ raise SettingNotBoolean.new('diff is not a boolean') unless [true, false].include?(diff)
13
+ raise SettingNotBoolean.new('force is not a boolean') unless [true, false].include?(force)
14
+ raise SettingNotBoolean.new('delete is not a boolean') unless [true, false].include?(delete)
15
+ raise SettingNotBoolean.new('interactive_progress is not a boolean') unless [true, false].include?(interactive_progress)
16
+ raise SettingNotBoolean.new('create_hosted_zones is not a boolean') unless [true, false].include?(create_hosted_zones)
17
+ end
18
+
19
+ def dry_run?
20
+ @dry_run
21
+ end
22
+
23
+ def diff?
24
+ @diff
25
+ end
26
+
27
+ def force?
28
+ @force
29
+ end
30
+
31
+ def delete?
32
+ @delete
33
+ end
34
+
35
+ def interactive_progress?
36
+ @interactive_progress
37
+ end
38
+
39
+ def create_hosted_zones?
40
+ @create_hosted_zones
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ class Array
2
+ def self.wrap(object)
3
+ if object.nil?
4
+ []
5
+ elsif object.respond_to?(:to_ary)
6
+ object.to_ary || [object]
7
+ else
8
+ [object]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ def zonify!(domain)
2
+ domain = "#{domain}." unless domain.end_with?('.')
3
+ domain
4
+ end
@@ -0,0 +1,100 @@
1
+ module SprinkleDNS
2
+ class EntryPolicyService
3
+ def initialize(hosted_zone, configuration)
4
+ @hosted_zone = hosted_zone
5
+ @configuration = configuration
6
+ end
7
+
8
+ def entries_to_create
9
+ @hosted_zone.entries.select{|hze| hze.referenced? && hze.new?}
10
+ end
11
+
12
+ def entries_to_update
13
+ @hosted_zone.entries.select{|hze| hze.referenced? && hze.changed? && !hze.new?}
14
+ end
15
+
16
+ def entries_not_touched
17
+ not_touched = @hosted_zone.entries.select{|hze| hze.referenced? && !hze.changed? && !hze.new?}
18
+
19
+ if @configuration.delete?
20
+ not_touched
21
+ else
22
+ (not_touched + @hosted_zone.entries.select{|hze| !hze.referenced?}).flatten
23
+ end
24
+ end
25
+
26
+ def entries_to_delete
27
+ if @configuration.delete?
28
+ @hosted_zone.entries.select{|hze| !hze.referenced?}
29
+ else
30
+ []
31
+ end
32
+ end
33
+
34
+ def entries_to_change
35
+ [entries_to_create, entries_to_update, entries_to_delete].sum(&:size)
36
+ end
37
+
38
+ def entries_to_change?
39
+ entries_to_change > 0
40
+ end
41
+
42
+ def compile
43
+ generate_change_batch
44
+ end
45
+
46
+ private
47
+
48
+ def generate_change_batch
49
+ change_batch_options = []
50
+
51
+ entries_to_delete.each do |entry|
52
+ change_batch_options << {
53
+ action: 'DELETE',
54
+ resource_record_set: entry_to_rrs(entry),
55
+ }
56
+ end
57
+
58
+ entries_to_update.each do |entry|
59
+ change_batch_options << {
60
+ action: 'UPSERT',
61
+ resource_record_set: entry_to_rrs(entry.new_entry),
62
+ }
63
+ end
64
+
65
+ entries_to_create.each do |entry|
66
+ change_batch_options << {
67
+ action: 'CREATE',
68
+ resource_record_set: entry_to_rrs(entry)
69
+ }
70
+ end
71
+
72
+ change_batch_options
73
+ end
74
+
75
+ private
76
+
77
+ def entry_to_rrs(entry)
78
+ case entry
79
+ when HostedZoneEntry
80
+ {
81
+ name: entry.name,
82
+ type: entry.type,
83
+ ttl: entry.ttl,
84
+ resource_records: entry.value.map{|a| {value: a}},
85
+ }
86
+ when HostedZoneAlias
87
+ {
88
+ name: entry.name,
89
+ type: entry.type,
90
+ alias_target: {
91
+ hosted_zone_id: entry.target_hosted_zone_id,
92
+ dns_name: entry.target_dns_name,
93
+ evaluate_target_health: false,
94
+ },
95
+ }
96
+ else raise "Unknown entry"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,9 @@
1
+ module SprinkleDNS
2
+ class RecordNotAString < StandardError; end
3
+ class RecordNotValid < StandardError; end
4
+ class TtlNotInteger < StandardError; end
5
+ class SettingNotBoolean < StandardError; end
6
+
7
+ class MissingHostedZones < StandardError; end
8
+ class DuplicatedHostedZones < StandardError; end
9
+ end
@@ -0,0 +1,36 @@
1
+ module SprinkleDNS
2
+ class HostedZone
3
+ attr_reader :name
4
+ attr_accessor :resource_record_sets
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ @resource_record_sets = []
9
+ end
10
+
11
+ def add_or_update_hosted_zone_entry(wanted_entry)
12
+ raise if wanted_entry.hosted_zone != self.name
13
+
14
+ existing_entry = @resource_record_sets.find{|hze| hze.type == wanted_entry.type && hze.name == wanted_entry.name}
15
+
16
+ if existing_entry
17
+ if existing_entry.persisted?
18
+ existing_entry.mark_referenced!
19
+ existing_entry.new_value(wanted_entry)
20
+ else
21
+ wanted_entry.mark_referenced!
22
+ @resource_record_sets[@resource_record_sets.index(existing_entry)] = wanted_entry
23
+ end
24
+ else
25
+ wanted_entry.mark_new!
26
+ wanted_entry.mark_referenced!
27
+ @resource_record_sets << wanted_entry
28
+ end
29
+ end
30
+
31
+ def entries
32
+ @resource_record_sets
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,91 @@
1
+ module SprinkleDNS
2
+ class HostedZoneAlias
3
+ attr_accessor :type, :name, :hosted_zone, :target_hosted_zone_id, :target_dns_name
4
+ attr_accessor :changed_type, :changed_name, :changed_target_hosted_zone_id, :changed_target_dns_name
5
+ attr_accessor :referenced, :persisted
6
+ attr_accessor :new_entry
7
+
8
+ def initialize(type, name, target_hosted_zone_id, target_dns_name, hosted_zone)
9
+ @type = type
10
+ @name = zonify!(name)
11
+ @target_hosted_zone_id = target_hosted_zone_id
12
+ @target_dns_name = target_dns_name
13
+ @hosted_zone = hosted_zone
14
+
15
+ @new_entry = nil
16
+
17
+ raise if [@type, @name, @target_hosted_zone_id, @target_dns_name, @hosted_zone].any?(&:nil?)
18
+
19
+ @changed_type = false
20
+ @changed_name = false
21
+ @changed_target_hosted_zone_id = false
22
+ @changed_target_dns_name = false
23
+ @referenced = false
24
+ @persisted = false
25
+ end
26
+
27
+ def mark_new!
28
+ @changed_type, @changed_name, @changed_target_hosted_zone_id, @changed_target_dns_name = [true, true, true, true]
29
+ end
30
+
31
+ def new?
32
+ [@changed_type, @changed_name, @changed_target_hosted_zone_id, @changed_target_dns_name].all?
33
+ end
34
+
35
+ def persisted!
36
+ @persisted = true
37
+ end
38
+
39
+ def persisted?
40
+ @persisted
41
+ end
42
+
43
+ def changed?
44
+ [@changed_type, @changed_name, @changed_target_hosted_zone_id, @changed_target_dns_name].any?
45
+ end
46
+
47
+ def mark_referenced!
48
+ @referenced = true
49
+ end
50
+
51
+ def referenced?
52
+ @referenced
53
+ end
54
+
55
+ def new_value(new_entry)
56
+ if new_entry.class == SprinkleDNS::HostedZoneAlias
57
+ @changed_target_hosted_zone_id = true if @target_hosted_zone_id != new_entry.target_hosted_zone_id
58
+ @changed_target_dns_name = true if @target_dns_name != new_entry.target_dns_name
59
+ else
60
+ @changed_target_hosted_zone_id = true
61
+ @changed_target_dns_name = true
62
+ end
63
+
64
+ # TODO test this
65
+ if @changed_target_hosted_zone_id || @changed_target_dns_name
66
+ @new_entry = new_entry
67
+ end
68
+
69
+ self.changed?
70
+ end
71
+
72
+ def to_s
73
+ [
74
+ "Alias",
75
+ sprintf("%4s", type),
76
+ sprintf("%30s", name),
77
+ sprintf("%10s", target_hosted_zone_id),
78
+ sprintf("%30s", target_dns_name),
79
+ sprintf("%6s", hosted_zone),
80
+ ].join(" ")
81
+ end
82
+
83
+ private
84
+
85
+ def valid_record_types
86
+ ['SOA','A','TXT','NS','CNAME','MX','NAPTR','PTR','SRV','SPF','AAAA']
87
+ end
88
+
89
+ end
90
+ end
91
+
@@ -0,0 +1,18 @@
1
+ module SprinkleDNS
2
+ class HostedZoneDomain
3
+ def self.parse(domain)
4
+ splitted = domain.split('.')
5
+
6
+ if two_dotted_domain?(domain)
7
+ [ splitted[-3], splitted[-2], splitted[-1] ].join('.') + '.'
8
+ else
9
+ [ splitted[-2], splitted[-1] ].join('.') + '.'
10
+ end
11
+ end
12
+
13
+ private
14
+ def self.two_dotted_domain?(domain)
15
+ true if domain.include?('.co.uk')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,97 @@
1
+ module SprinkleDNS
2
+ class HostedZoneEntry
3
+ attr_accessor :type, :name, :value, :ttl, :hosted_zone
4
+ attr_accessor :changed_type, :changed_name, :changed_value, :changed_ttl
5
+ attr_accessor :referenced, :persisted
6
+ attr_accessor :new_entry
7
+
8
+ def initialize(type, name, value, ttl, hosted_zone)
9
+ @type = type
10
+ @name = zonify!(name)
11
+ @value = Array.wrap(value)
12
+ @ttl = ttl
13
+ @hosted_zone = hosted_zone
14
+
15
+ @new_entry = nil
16
+
17
+ raise if [@type, @name, @value, @ttl, @hosted_zone].any?(&:nil?)
18
+ raise SprinkleDNS::RecordNotAString.new('Record-type should be a string') unless @type.is_a?(String)
19
+ raise SprinkleDNS::RecordNotValid.new("Record #{@type} is not supported") if !valid_record_types.include?(@type)
20
+ raise SprinkleDNS::TtlNotInteger.new('TTL should be an integer') unless @ttl.is_a?(Integer)
21
+
22
+ @changed_type = false
23
+ @changed_name = false
24
+ @changed_value = false
25
+ @changed_ttl = false
26
+ @referenced = false
27
+ @persisted = false
28
+
29
+ if ['CNAME', 'MX'].include?(type)
30
+ @value = @value.map!{|v| zonify!(v)}
31
+ end
32
+ end
33
+
34
+ def mark_new!
35
+ @changed_type, @changed_name, @changed_value, @changed_ttl = [true, true, true, true]
36
+ end
37
+
38
+ def new?
39
+ [@changed_type, @changed_name, @changed_value, @changed_ttl].all?
40
+ end
41
+
42
+ def persisted!
43
+ @persisted = true
44
+ end
45
+
46
+ def persisted?
47
+ @persisted
48
+ end
49
+
50
+ def changed?
51
+ [@changed_type, @changed_name, @changed_value, @changed_ttl].any?
52
+ end
53
+
54
+ def mark_referenced!
55
+ @referenced = true
56
+ end
57
+
58
+ def referenced?
59
+ @referenced
60
+ end
61
+
62
+ def new_value(new_entry)
63
+ if new_entry.class == SprinkleDNS::HostedZoneEntry
64
+ @changed_value = true if @value != new_entry.value
65
+ @changed_ttl = true if @ttl != new_entry.ttl
66
+ else
67
+ @changed_value = true
68
+ @changed_ttl = true
69
+ end
70
+
71
+ # TODO test this
72
+ if @changed_value || @changed_ttl
73
+ @new_entry = new_entry
74
+ end
75
+
76
+ self.changed?
77
+ end
78
+
79
+ def to_s
80
+ [
81
+ "Entry",
82
+ sprintf("%4s", type),
83
+ sprintf("%30s", name),
84
+ sprintf("%50s", value.join(", ")),
85
+ sprintf("%6s", ttl),
86
+ sprintf("%6s", hosted_zone),
87
+ ].join(" ")
88
+ end
89
+
90
+ private
91
+
92
+ def valid_record_types
93
+ ['SOA','A','TXT','NS','CNAME','MX','NAPTR','PTR','SRV','SPF','AAAA']
94
+ end
95
+
96
+ end
97
+ end