sprinkle_dns 1.0.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.
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