dns-zone 0.0.0.alpha

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 82c71fa4e618b0bff6f946e6097511699e49aec0
4
+ data.tar.gz: b4021e5692ea47f2ca8a3d129aed828593155af7
5
+ SHA512:
6
+ metadata.gz: f162ed2a6e7804120dd8b6fadac2c5b4c6d2a51b9044e66dc8e48fc2c86e1dbad34f89b185934febb26cc41d6f032dfff742bf7254b5fd553cbf77363d82653c
7
+ data.tar.gz: 4242b9bbf4fe966a790e948ff5e31affc9488ac0fe5c6f6dd1ae2e92cd4b4e2f68f04bfe2f0bb685e7f8eac15e2420855568da9419a1849534c43e089022acd3
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ # Run minitest suite
2
+ guard :minitest do
3
+ watch(%r{^dns-zone\.gemspec}) { 'all' }
4
+ watch(%r{^lib/dns/(.*)\.rb}) { |m| "test/#{m[1]}_test.rb" }
5
+ watch(%r{^lib/dns/zone/test_case\.rb}) { 'all' }
6
+ watch(%r{^lib/dns/zone/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
7
+ watch(%r{^test/(.*/)*(.*)_test\.rb})
8
+ end
9
+
10
+ # Run `bundle update|install` when gem files altered.
11
+ guard :bundler do
12
+ watch('Gemfile')
13
+ watch(/^.+\.gemspec/)
14
+ end
data/HISTORY.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.0.0 (2014-02-16)
2
+
3
+ * Initial development/hacking initiated.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ dns-zone
2
+ ========
3
+
4
+ A Ruby library for building and parsing DNS zone files.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your Gemfile:
9
+
10
+ gem 'dns-zone'
11
+
12
+ And then execute:
13
+
14
+ bundle install
15
+
16
+ Require the gem in your code:
17
+
18
+ require 'dns/zone'
19
+
20
+ ## Usage
21
+
22
+ DNS::Zone.new
23
+ DNS::Zone.load(zone_as_string)
24
+ DNS::Zone::RR.load(rr_as_string)
25
+ DNS::Zone::RR::A.load(a_rr_as_string)
26
+
27
+ zone = DNS::Zone.new
28
+ zone.origin = 'example.com.'
29
+ # FIXME: not sure what RFC (if any) defines the time formatting
30
+ zone.ttl = '1d'
31
+ # FIXME: keep DNS style representation? for <domain-name>s and email addresses
32
+ zone.soa.nameserver = 'ns0.lividpenguin.com.'
33
+ zone.soa.email = 'hostmaster.lividpenguin.com.'
34
+
35
+ # output as dns zone file
36
+ zone.to_zone_file
37
+
38
+ # Development
39
+
40
+ ## Development Commands
41
+
42
+ # install external gem dependencies first
43
+ bundle install
44
+
45
+ # run all tests and build code coverage
46
+ bundle exec rake test
47
+
48
+ # hints where to improve docs
49
+ bundle exec inch
50
+
51
+ # watch for changes and run development commands (tests, documentation, etc)
52
+ bundle exec guard
53
+
54
+ # TODO
55
+
56
+ ## Must have
57
+
58
+ [ ] Ability to load a whole zone
59
+ [x] Add support for RR Type: SOA
60
+ [ ] Add support for RR Type: PTR
61
+ [ ] Add support for RR Type: SPF
62
+ [ ] Add support for RR Type: LOC
63
+ [ ] Add support for RR Type: HINFO
64
+
65
+ ## Would be nice
66
+
67
+ [ ] Handle parsing a zone file that uses more then one $ORIGIN directive.
68
+ [ ] Validation, error checking...
69
+ [ ] Only one SOA per zone.
70
+ [ ] CNAMEs can't use a label of `@`.
71
+
72
+ [ ] Ability to 'include' defaults/records into a zone.
73
+ This may or may not want to mean supporting the `$INCLUDE` directive.
74
+
75
+ ## At some point; low priority
76
+
77
+ [ ] Configuration options:
78
+ [ ] spaces/tabs used between RR params in zone file output
79
+ [ ] time format to use (seconds or bind time format (e.g. 1d))
80
+ [ ] add comments to explain TTL's that are in seconds
81
+ [ ] Ability to add comment to RR (n.b. currently we strip comments when parsing)
82
+ [ ] Add support for RR Type: DNAME
83
+ [ ] Add support for RR Type: DNSKEY
84
+ [ ] Add support for RR Type: DS
85
+ [ ] Add support for RR Type: KEY
86
+ [ ] Add support for RR Type: NSEC
87
+ [ ] Add support for RR Type: RRSIG
88
+ [ ] Add support for RR Type: NAPTR
89
+ [ ] Add support for RR Type: RP
90
+ [ ] Add support for RR Type: RT
91
+
92
+ # Notes
93
+
94
+ - RR Format: `[<TTL>] [<class>] <type> <RDATA>`
95
+ - A DNS zone is built from RR's and a couple of other special directives.
96
+ - If zone file does not include $ORIGIN, it will be inferred by the `zone "<zone-name>" {}` clause from bind.conf
97
+ In general we should always explicitly define an $ORIGIN directive unless there is a very good reason not to.
98
+ - [RFC 1035 - Domain Names - Implementation and Specification](http://www.ietf.org/rfc/rfc1035.txt)
99
+ - [RFC 2308 - Negative Caching of DNS Queries (DNS NCACHE)](http://www.ietf.org/rfc/rfc2308.txt)
100
+ - [RFC 3596 - DNS Extensions to Support IP Version 6](http://www.ietf.org/rfc/rfc3596.txt)
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'bundler'
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+
6
+ # by default run unit tests.
7
+ task :default => 'test'
8
+
9
+ desc 'Run full test suite and generate code coverage -- COVERAGE=false to disable code coverage'
10
+ Rake::TestTask.new(:test) do |task|
11
+ ENV['COVERAGE'] ||= 'yes'
12
+ task.libs << 'test'
13
+ task.pattern = 'test/**/*_test.rb'
14
+ task.verbose = true
15
+ end
data/dns-zone.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ require './lib/dns/zone/version'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ # gem information/details
5
+ s.name = 'dns-zone'
6
+ s.version = DNS::Zone::Version
7
+ s.date = Time.now.strftime('%Y-%m-%d')
8
+ s.summary = 'A Ruby library for building and parsing DNS zone files.'
9
+ s.license = 'MIT'
10
+ s.homepage = 'https://github.com/lantins/dns-zone'
11
+ s.authors = ['Luke Antins']
12
+ s.email = ['luke@lividpenguin.com']
13
+
14
+ # gem settings for what files to include.
15
+ s.files = %w(Rakefile README.md HISTORY.md Gemfile Guardfile dns-zone.gemspec)
16
+ s.files += Dir.glob('{test/**/*,lib/**/*}')
17
+ s.require_paths = ['lib']
18
+ #s.executables = ['dns-zone']
19
+ #s.default_executable = 'dns-zone'
20
+
21
+ # min ruby version
22
+ s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
23
+
24
+ # cross platform gem dependencies
25
+ #s.add_dependency('gli')
26
+ #s.add_dependency('paint')
27
+ s.add_development_dependency('bundler', '~> 0')
28
+ s.add_development_dependency('rake', '~> 0')
29
+ s.add_development_dependency('minitest', '~> 0')
30
+ s.add_development_dependency('simplecov', '~> 0.7')
31
+ s.add_development_dependency('yard', '~> 0')
32
+ s.add_development_dependency('inch', '~> 0')
33
+ s.add_development_dependency('guard-minitest', '~> 0')
34
+ s.add_development_dependency('guard-bundler', '~> 0')
35
+
36
+ # long description.
37
+ s.description = <<-EOL
38
+ A Ruby library for building and parsing DNS zone files for use with
39
+ Bind and PowerDNS (with Bind backend) DNS servers.
40
+ EOL
41
+ end
data/lib/dns/zone.rb ADDED
@@ -0,0 +1,105 @@
1
+ require 'dns/zone/rr'
2
+ require 'dns/zone/version'
3
+
4
+ # :nodoc:
5
+ module DNS
6
+
7
+ # Represents a 'whole' zone of many resource records (RRs).
8
+ #
9
+ # This is also the primary namespace for the `dns-zone` gem.
10
+ class Zone
11
+
12
+ attr_accessor :ttl, :origin, :records
13
+
14
+ def initialize
15
+ @records = []
16
+ end
17
+
18
+ # FROM RFC:
19
+ # The format of these files is a sequence of entries. Entries are
20
+ # predominantly line-oriented, though parentheses can be used to continue
21
+ # a list of items across a line boundary, and text literals can contain
22
+ # CRLF within the text. Any combination of tabs and spaces act as a
23
+ # delimiter between the separate items that make up an entry. The end of
24
+ # any line in the master file can end with a comment. The comment starts
25
+ # with a ";" (semicolon).
26
+
27
+ def self.load(string)
28
+ # get entries
29
+ entries = self.extract_entries(string)
30
+
31
+ instance = self.new
32
+
33
+ entries.each do |entry|
34
+ if entry =~ /\$(ORIGIN|TTL)\s+(.+)/
35
+ instance.ttl = $2 if $1 == 'TTL'
36
+ instance.origin = $2 if $1 == 'ORIGIN'
37
+ next
38
+ end
39
+
40
+ if entry =~ DNS::Zone::RR::RX_RR
41
+ rec = DNS::Zone::RR.load(entry)
42
+ instance.records << rec if rec
43
+ end
44
+
45
+ end
46
+
47
+ # read in special statments like $TTL and $ORIGIN
48
+ # parse each RR and create a Ruby object for it
49
+ return instance
50
+ end
51
+
52
+ def self.extract_entries(string)
53
+ entries = []
54
+ mode = :line
55
+ entry = ''
56
+
57
+ parentheses_ref_count = 0
58
+
59
+ string.lines.each do |line|
60
+ # strip comments unless escaped
61
+ line = line.gsub(/(?<!\\);.*/o, '').chomp
62
+
63
+ next if line.gsub(/\s+/, '').empty?
64
+
65
+ # append to entry line
66
+ entry << line
67
+
68
+ quotes = entry.count('"')
69
+ has_quotes = quotes > 0
70
+
71
+ parentheses = entry.count('()')
72
+ has_parentheses = parentheses > 0
73
+
74
+ if has_quotes
75
+ character_strings = entry.scan(/("(?:[^"\\]+|\\.)*")/).join(' ')
76
+ without = entry.gsub(/"((?:[^"\\]+|\\.)*)"/, '')
77
+ parentheses_ref_count = without.count('(') - without.count(')')
78
+ else
79
+ parentheses_ref_count = entry.count('(') - entry.count(')')
80
+ end
81
+
82
+ # are parentheses balanced?
83
+ if parentheses_ref_count == 0
84
+ if has_quotes
85
+ without.gsub!(/[()]/, '')
86
+ without.gsub!(/[ ]{2,}/, ' ')
87
+ #entries << (without + character_strings)
88
+ entry = (without + character_strings)
89
+ else
90
+ entry.gsub!(/[()]/, '')
91
+ entry.gsub!(/[ ]{2,}/, ' ')
92
+ entry.gsub!(/[ ]+$/, '')
93
+ #entries << entry
94
+ end
95
+ entries << entry
96
+ entry = ''
97
+ end
98
+
99
+ end
100
+
101
+ return entries
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,51 @@
1
+ module DNS
2
+ class Zone
3
+
4
+ # The module containes resource record types supported by this gem.
5
+ # The #{load} method will convert RR string data into a Ruby class.
6
+ module RR
7
+
8
+ RX_TTL = /\d+[wdmhs]?/i
9
+ RX_KLASS = /(?<klass>IN)?/i
10
+ RX_TYPE = /(?<type>A|AAAA|CNAME|MX|NS|SOA|SRV|TXT)\s{1}/i
11
+ RX_RR = /^(?<label>\S+|\s{1})\s*(?<ttl>#{RX_TTL})?\s*#{RX_KLASS}\s*#{RX_TYPE}\s*(?<rdata>[\s\S]*)$/i
12
+ RX_DOMAINNAME = /\S+\./i
13
+
14
+ # Load RR string data and return an instance representing the RR.
15
+ #
16
+ # @param string [String] RR ASCII string data
17
+ # @param options [Hash] additional data required to correctly parse a 'whole' zone
18
+ # @option options [String] :last_label The last label used by the previous RR
19
+ # @return [Object]
20
+ def self.load(string, options = {})
21
+ # strip comments, unless its escaped.
22
+ string.gsub!(/(?<!\\);.*/o, "");
23
+
24
+ captures = string.match(RX_RR)
25
+ return nil unless captures
26
+
27
+ case captures[:type]
28
+ when 'A' then A.new.load(string, options)
29
+ when 'AAAA' then AAAA.new.load(string, options)
30
+ when 'TXT' then TXT.new.load(string, options)
31
+ when 'SOA' then SOA.new.load(string, options)
32
+ when 'NS' then NS.new.load(string, options)
33
+ else
34
+ raise 'Unknown RR Type'
35
+ end
36
+ end
37
+
38
+ autoload :Record, 'dns/zone/rr/record'
39
+
40
+ autoload :A, 'dns/zone/rr/a'
41
+ autoload :AAAA, 'dns/zone/rr/aaaa'
42
+ autoload :CNAME, 'dns/zone/rr/cname'
43
+ autoload :MX, 'dns/zone/rr/mx'
44
+ autoload :NS, 'dns/zone/rr/ns'
45
+ autoload :SOA, 'dns/zone/rr/soa'
46
+ autoload :SRV, 'dns/zone/rr/srv'
47
+ autoload :TXT, 'dns/zone/rr/txt'
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ # `A` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::A < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :address
7
+
8
+ def to_s
9
+ parts = general_prefix
10
+ parts << address
11
+ parts.join(' ')
12
+ end
13
+
14
+ def load(string, options = {})
15
+ rdata = load_general_and_get_rdata(string, options)
16
+ return nil unless rdata
17
+ @address = rdata
18
+ self
19
+ end
20
+
21
+ end
@@ -0,0 +1,5 @@
1
+ # `AAAA` resource record.
2
+ #
3
+ # RFC xxxx
4
+ class DNS::Zone::RR::AAAA < DNS::Zone::RR::A
5
+ end
@@ -0,0 +1,14 @@
1
+ # `CNAME` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::CNAME < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :domainname
7
+
8
+ def to_s
9
+ parts = general_prefix
10
+ parts << domainname
11
+ parts.join(' ')
12
+ end
13
+
14
+ end
@@ -0,0 +1,16 @@
1
+ # `MX` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::MX < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :preference
7
+ attr_accessor :exchange
8
+
9
+ def to_s
10
+ parts = general_prefix
11
+ parts << preference
12
+ parts << exchange
13
+ parts.join(' ')
14
+ end
15
+
16
+ end
@@ -0,0 +1,21 @@
1
+ # `NS` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::NS < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :nameserver
7
+
8
+ def to_s
9
+ parts = general_prefix
10
+ parts << nameserver
11
+ parts.join(' ')
12
+ end
13
+
14
+ def load(string, options = {})
15
+ rdata = load_general_and_get_rdata(string, options)
16
+ return nil unless rdata
17
+ @nameserver = rdata
18
+ self
19
+ end
20
+
21
+ end
@@ -0,0 +1,81 @@
1
+ # Parent class of all RR types, common resource record code lives here.
2
+ # Is responsible for building a Ruby object given a RR string.
3
+ #
4
+ # @abstract Each RR TYPE should subclass and override: {#load} and #{to_s}
5
+ class DNS::Zone::RR::Record
6
+
7
+ attr_accessor :label, :ttl
8
+ attr_reader :klass
9
+
10
+ def initialize
11
+ @label = '@'
12
+ @klass = 'IN'
13
+ end
14
+
15
+ # FIXME: should we just: `def type; 'SOA'; end` rather then do the class name convension?
16
+ #
17
+ # Figures out TYPE of RR using class name.
18
+ # This means the class name _must_ match the RR ASCII TYPE.
19
+ #
20
+ # When called directly on the parent class (that you should never do), it will
21
+ # return the string as `<type>`, for use with internal tests.
22
+ #
23
+ # @return [String] the RR type
24
+ def type
25
+ name = self.class.name.split('::').last
26
+ return '<type>' if name == 'Record'
27
+ name
28
+ end
29
+
30
+ # Returns 'general' prefix (in parts) that come before the RDATA.
31
+ # Used by all RR types, generates: `[<label>] [<ttl>] [<class>] <type>`
32
+ #
33
+ # @return [Array<String>] rr prefix parts
34
+ def general_prefix
35
+ parts = []
36
+ parts << label
37
+ parts << ttl if ttl
38
+ parts << 'IN'
39
+ parts << type
40
+ parts
41
+ end
42
+
43
+ # Build RR zone file output.
44
+ #
45
+ # @return [String] RR zone file output
46
+ def to_s
47
+ general_prefix.join(' ')
48
+ end
49
+
50
+ # @abstract Override to update instance with RR type spesific data.
51
+ # @param string [String] RR ASCII string data
52
+ # @param options [Hash] additional data required to correctly parse a 'whole' zone
53
+ # @option options [String] :last_label The last label used by the previous RR
54
+ # @return [Object]
55
+ def load(string, options = {})
56
+ raise 'must be implemented by subclass'
57
+ end
58
+
59
+ # Load 'general' RR data/params and return the remaining RDATA for further parsing.
60
+ #
61
+ # @param string [String] RR ASCII string data
62
+ # @param options [Hash] additional data required to correctly parse a 'whole' zone
63
+ # @return [String] remaining RDATA
64
+ def load_general_and_get_rdata(string, options = {})
65
+ # strip comments, unless its escaped.
66
+ string.gsub!(/(?<!\\);.*/o, "");
67
+
68
+ captures = string.match(DNS::Zone::RR::RX_RR)
69
+ return nil unless captures
70
+
71
+ if captures[:label] == ' '
72
+ @label = options[:last_label]
73
+ else
74
+ @label = captures[:label]
75
+ end
76
+
77
+ @ttl = captures[:ttl]
78
+ captures[:rdata]
79
+ end
80
+
81
+ end
@@ -0,0 +1,52 @@
1
+ # `SRV` resource record.
2
+ #
3
+ # RFC xxxx
4
+ class DNS::Zone::RR::SOA < DNS::Zone::RR::Record
5
+
6
+ RX_SOA_RDATA = %r{
7
+ (?<nameserver>#{DNS::Zone::RR::RX_DOMAINNAME})\s* # get nameserver domainname
8
+ (?<email>#{DNS::Zone::RR::RX_DOMAINNAME})\s* # get mailbox domainname
9
+ (?<serial>\d+)\s*
10
+ (?<refresh_ttl>#{DNS::Zone::RR::RX_TTL})\s*
11
+ (?<retry_ttl>#{DNS::Zone::RR::RX_TTL})\s*
12
+ (?<expiry_ttl>#{DNS::Zone::RR::RX_TTL})\s*
13
+ (?<minimum_ttl>#{DNS::Zone::RR::RX_TTL})\s*
14
+ }mx
15
+
16
+ attr_accessor :nameserver, :email, :serial, :refresh_ttl, :retry_ttl, :expiry_ttl, :minimum_ttl
17
+
18
+ def to_s
19
+ parts = general_prefix
20
+ parts << nameserver
21
+ parts << email
22
+
23
+ parts << '('
24
+ parts << serial
25
+ parts << refresh_ttl
26
+ parts << retry_ttl
27
+ parts << expiry_ttl
28
+ parts << minimum_ttl
29
+ parts << ')'
30
+ parts.join(' ')
31
+ end
32
+
33
+ def load(string, options = {})
34
+ rdata = load_general_and_get_rdata(string, options)
35
+ return nil unless rdata
36
+
37
+ captures = rdata.match(RX_SOA_RDATA)
38
+ return nil unless captures
39
+
40
+ @nameserver = captures[:nameserver]
41
+ @email = captures[:email]
42
+ @serial = captures[:serial].to_i
43
+ @refresh_ttl = captures[:refresh_ttl]
44
+ @retry_ttl = captures[:retry_ttl]
45
+ @expiry_ttl = captures[:expiry_ttl]
46
+ @minimum_ttl = captures[:minimum_ttl]
47
+
48
+
49
+ self
50
+ end
51
+
52
+ end
@@ -0,0 +1,17 @@
1
+ # `SRV` resource record.
2
+ #
3
+ # RFC xxxx
4
+ class DNS::Zone::RR::SRV < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :priority, :weight, :port, :target
7
+
8
+ def to_s
9
+ parts = general_prefix
10
+ parts << priority
11
+ parts << weight
12
+ parts << port
13
+ parts << target
14
+ parts.join(' ')
15
+ end
16
+
17
+ end
@@ -0,0 +1,22 @@
1
+ # `A` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::TXT < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :text
7
+
8
+ def to_s
9
+ parts = general_prefix
10
+ parts << %Q{"#{text}"}
11
+ parts.join(' ')
12
+ end
13
+
14
+ def load(string, options = {})
15
+ rdata = load_general_and_get_rdata(string, options)
16
+ return nil unless rdata
17
+ # extract text from within quotes; allow multiple quoted strings; ignore escaped quotes
18
+ @text = rdata.scan(/"((?:[^"\\]+|\\.)*)"/).join
19
+ self
20
+ end
21
+
22
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # test_case.rb - A file used to setup the testing enviroment for the library.
3
+ #
4
+
5
+ require 'rubygems'
6
+
7
+ # --- code coverage on MRI 1.9 ruby only, but disabled by default --------------
8
+ if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'ruby' && ENV['COVERAGE']
9
+ require 'simplecov'
10
+ #SimpleCov.command_name 'test:unit'
11
+ SimpleCov.start do
12
+ # code coverage groups.
13
+ add_filter 'test/'
14
+ end
15
+ end
16
+
17
+ # --- load our dependencies using bundler --------------------------------------
18
+ require 'bundler/setup'
19
+ Bundler.setup
20
+ require 'minitest/autorun'
21
+ require 'minitest/pride'
22
+
23
+ # --- Load lib to test ---------------------------------------------------------
24
+ require 'dns/zone'
25
+
26
+ # --- Extend DNS::Zone::TestCase -------------------------------------------------
27
+ class DNS::Zone::TestCase < Minitest::Test
28
+ end
@@ -0,0 +1,6 @@
1
+ module DNS
2
+ class Zone
3
+ # Version number (major.minor.tiny)
4
+ Version = '0.0.0.alpha'
5
+ end
6
+ end
data/test/rr/a_test.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_A_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__a
6
+ rr = DNS::Zone::RR::A.new
7
+
8
+ # ensure we can set address parameter
9
+ rr.address = '10.0.1.1'
10
+ assert_equal '@ IN A 10.0.1.1', rr.to_s
11
+ rr.address = '10.0.2.2'
12
+ assert_equal '@ IN A 10.0.2.2', rr.to_s
13
+
14
+ # with a label set
15
+ rr.label = 'labelname'
16
+ assert_equal 'labelname IN A 10.0.2.2', rr.to_s
17
+
18
+ # with a ttl
19
+ rr.ttl = '4w'
20
+ assert_equal 'labelname 4w IN A 10.0.2.2', rr.to_s
21
+ end
22
+
23
+ def test_load
24
+ rr = DNS::Zone::RR::A.new.load('@ IN A 127.0.0.1')
25
+ assert_equal '@', rr.label
26
+ assert_equal 'A', rr.type
27
+ assert_equal '127.0.0.1', rr.address
28
+
29
+ rr = DNS::Zone::RR::A.new.load('www IN A 127.0.0.1')
30
+ assert_equal 'www', rr.label
31
+ assert_equal 'A', rr.type
32
+ assert_equal 'IN', rr.klass
33
+ assert_equal '127.0.0.1', rr.address
34
+ end
35
+
36
+ end
@@ -0,0 +1,13 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_AAAA_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__aaaa
6
+ rr = DNS::Zone::RR::AAAA.new
7
+
8
+ # ensure we can set address parameter
9
+ rr.address = '2001:db8::3'
10
+ assert_equal '@ IN AAAA 2001:db8::3', rr.to_s
11
+ end
12
+
13
+ end
@@ -0,0 +1,12 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_CNAME_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__cname
6
+ rr = DNS::Zone::RR::CNAME.new
7
+ rr.label = 'google9d97d7f266ee521d'
8
+ rr.domainname = 'google.com.'
9
+ assert_equal 'google9d97d7f266ee521d IN CNAME google.com.', rr.to_s
10
+ end
11
+
12
+ end
@@ -0,0 +1,18 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_MX_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__mx
6
+ rr = DNS::Zone::RR::MX.new
7
+
8
+ # ensure we can set preference and exchange parameter
9
+ rr.preference = '10'
10
+ rr.exchange = 'mx0.lividpenguin.com.'
11
+ assert_equal '@ IN MX 10 mx0.lividpenguin.com.', rr.to_s
12
+
13
+ rr.preference = '20'
14
+ rr.exchange = 'mx1.lividpenguin.com.'
15
+ assert_equal '@ IN MX 20 mx1.lividpenguin.com.', rr.to_s
16
+ end
17
+
18
+ end
@@ -0,0 +1,11 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_NS_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__ns
6
+ rr = DNS::Zone::RR::NS.new
7
+ rr.nameserver = 'ns0.lividpenguin.com.'
8
+ assert_equal '@ IN NS ns0.lividpenguin.com.', rr.to_s
9
+ end
10
+
11
+ end
@@ -0,0 +1,30 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_Record_Test < DNS::Zone::TestCase
4
+
5
+ def test_rr_record_defaults
6
+ rr = DNS::Zone::RR::Record.new
7
+ assert_equal '@', rr.label, 'label is @, by default'
8
+ assert_equal nil, rr.ttl, 'ttl is nil, by default'
9
+ end
10
+
11
+ def test_rr_record_with_label
12
+ rr = DNS::Zone::RR::Record.new
13
+ rr.label = 'labelname'
14
+ assert_equal 'labelname IN <type>', rr.to_s
15
+ end
16
+
17
+ def test_rr_record_with_label_and_ttl
18
+ rr = DNS::Zone::RR::Record.new
19
+ rr.label = 'labelname'
20
+ rr.ttl = '2d'
21
+ assert_equal 'labelname 2d IN <type>', rr.to_s
22
+ end
23
+
24
+ def test_rr_record_with_ttl
25
+ rr = DNS::Zone::RR::Record.new
26
+ rr.ttl = '2d'
27
+ assert_equal '@ 2d IN <type>', rr.to_s
28
+ end
29
+
30
+ end
@@ -0,0 +1,34 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_SRV_Test < DNS::Zone::TestCase
4
+
5
+ EXAMPLE_SOA_IN = '@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. 2014021601 3h 15m 4w 30m'
6
+ EXAMPLE_SOA_OUT = '@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. ( 2014021601 3h 15m 4w 30m )'
7
+
8
+ def test_build
9
+ rr = DNS::Zone::RR::SOA.new
10
+ rr.nameserver = 'ns0.lividpenguin.com.'
11
+ rr.email = 'luke.lividpenguin.com.'
12
+ rr.serial = 2014_02_16_01
13
+ rr.refresh_ttl = '3h'
14
+ rr.retry_ttl = '15m'
15
+ rr.expiry_ttl = '4w'
16
+ rr.minimum_ttl = '30m'
17
+ assert_equal EXAMPLE_SOA_OUT, rr.to_s
18
+ end
19
+
20
+ def test_load
21
+ rr = DNS::Zone::RR::SOA.new.load(EXAMPLE_SOA_IN)
22
+ assert_equal '@', rr.label
23
+ assert_equal 'SOA', rr.type
24
+
25
+ assert_equal 'ns0.lividpenguin.com.', rr.nameserver
26
+ assert_equal 'luke.lividpenguin.com.', rr.email
27
+ assert_equal 2014_02_16_01, rr.serial
28
+ assert_equal '3h', rr.refresh_ttl
29
+ assert_equal '15m', rr.retry_ttl
30
+ assert_equal '4w', rr.expiry_ttl
31
+ assert_equal '30m', rr.minimum_ttl
32
+ end
33
+
34
+ end
@@ -0,0 +1,14 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_SRV_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__srv
6
+ rr = DNS::Zone::RR::SRV.new
7
+ rr.priority = 5
8
+ rr.weight = 0
9
+ rr.port = 5269
10
+ rr.target = 'xmpp-server.l.google.com.'
11
+ assert_equal '@ IN SRV 5 0 5269 xmpp-server.l.google.com.', rr.to_s
12
+ end
13
+
14
+ end
@@ -0,0 +1,44 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_TXT_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__txt
6
+ rr = DNS::Zone::RR::TXT.new
7
+
8
+ # ensure we can set text parameter
9
+ rr.text = 'test text'
10
+ assert_equal '@ IN TXT "test text"', rr.to_s
11
+
12
+ # with a label set
13
+ rr.label = 'labelname'
14
+ assert_equal 'labelname IN TXT "test text"', rr.to_s
15
+
16
+ # with a ttl
17
+ rr.ttl = '2w'
18
+ assert_equal 'labelname 2w IN TXT "test text"', rr.to_s
19
+ end
20
+
21
+ def test_load
22
+ rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "test text"')
23
+ assert_equal 'txtrecord', rr.label
24
+ assert_equal 'IN', rr.klass
25
+ assert_equal 'TXT', rr.type
26
+ assert_equal 'test text', rr.text
27
+ end
28
+
29
+ def test_load_multiple_quoted_strings
30
+ rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "part1 yo" " part2 yo"')
31
+ assert_equal 'part1 yo part2 yo', rr.text
32
+ end
33
+
34
+ def test_load_string_with_quotes
35
+ rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "we have \"double\" quotes"')
36
+ assert_equal %q{we have \"double\" quotes}, rr.text
37
+ end
38
+
39
+ def test_load_multiple_strings_with_quotes
40
+ rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "part1 " "we have \"double\" quotes" " part3"')
41
+ assert_equal %q{part1 we have \"double\" quotes part3}, rr.text
42
+ end
43
+
44
+ end
data/test/rr_test.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RRTest < DNS::Zone::TestCase
4
+
5
+ def test_load_ignores_comments
6
+ rr = DNS::Zone::RR.load('; comment lines are ignored')
7
+ assert_nil rr, 'should be nil when its a comment line'
8
+ end
9
+
10
+ def test_load_ignores_unparsable_input
11
+ rr = DNS::Zone::RR.load('invalid input should not be parsed')
12
+ assert_nil rr, 'should be nil when input cant be parsed at all'
13
+ end
14
+
15
+ def test_load_a_rr
16
+ rr = DNS::Zone::RR.load('www IN A 10.2.3.1')
17
+ assert_instance_of DNS::Zone::RR::A, rr, 'should be instance of A RR'
18
+ assert_equal 'www', rr.label
19
+ assert_equal 'A', rr.type
20
+ assert_equal '10.2.3.1', rr.address
21
+ end
22
+
23
+ def test_load_txt_rr
24
+ rr = DNS::Zone::RR.load('mytxt IN TXT "test text"')
25
+ assert_instance_of DNS::Zone::RR::TXT, rr, 'should be instance of TXT RR'
26
+ assert_equal 'mytxt', rr.label
27
+ assert_equal 'TXT', rr.type
28
+ assert_equal 'test text', rr.text
29
+ end
30
+
31
+ def test_load_a_rr_with_options_hash
32
+ rr = DNS::Zone::RR.load(' IN A 10.2.3.1', { last_label: 'www' })
33
+ assert_equal 'www', rr.label
34
+
35
+ rr = DNS::Zone::RR.load('blog IN A 10.2.3.1', { last_label: 'www' })
36
+ assert_equal 'blog', rr.label
37
+
38
+ rr = DNS::Zone::RR.load('@ IN A 10.2.3.1', { last_label: 'mail' })
39
+ assert_equal '@', rr.label
40
+ end
41
+
42
+ end
@@ -0,0 +1,9 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class VersionTest < DNS::Zone::TestCase
4
+
5
+ def test_should_have_a_version
6
+ assert DNS::Zone::Version, 'unable to access the version number'
7
+ end
8
+
9
+ end
data/test/zone_test.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class ZoneTest < DNS::Zone::TestCase
4
+
5
+ ZONE_EXAMPLE_SIMPLE =<<-EOL
6
+ $ORIGIN lividpenguin.com.
7
+ $TTL 3d
8
+ @ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. (
9
+ 2013101406 ; zone serial number
10
+ 12h ; refresh ttl
11
+ 15m ; retry ttl
12
+ 3w ; expiry ttl
13
+ 3h ; minimum ttl
14
+ )
15
+
16
+ ; a more difficult ; comment ( its trying to break things!
17
+
18
+ @ IN NS ns0.lividpenguin.com.
19
+ @ IN NS ns1.lividpenguin.com.
20
+ @ IN NS ns2.lividpenguin.com.
21
+
22
+ @ IN A 77.75.105.197
23
+ @ IN AAAA 2a01:348::6:4d4b:69c5:0:1
24
+
25
+ foo IN TXT "part1""part2"
26
+ bar IN TXT ("part1 "
27
+ "part2 "
28
+ "part3")
29
+ EOL
30
+
31
+ def test_create_new_instance
32
+ assert DNS::Zone.new
33
+ end
34
+
35
+ def test_load_simple_zone
36
+ zone = DNS::Zone.load(ZONE_EXAMPLE_SIMPLE)
37
+ assert_equal '3d', zone.ttl, 'check ttl matches example input'
38
+ assert_equal 'lividpenguin.com.', zone.origin, 'check origin matches example input'
39
+ assert_equal 8, zone.records.length, 'we should have 8 records (including SOA)'
40
+
41
+ p ''
42
+ zone.records.each do |rec|
43
+ p rec
44
+ end
45
+ end
46
+
47
+ def test_extract_entry_from_one_line
48
+ entries = DNS::Zone.extract_entries(%Q{maiow IN TXT "purr"})
49
+ assert_equal 1, entries.length, 'we should have 1 entry'
50
+ assert_equal 'maiow IN TXT "purr"', entries[0], 'entry should match expected'
51
+ end
52
+
53
+ def test_extract_entry_should_ignore_comments
54
+ entries = DNS::Zone.extract_entries(%Q{maiow IN TXT "purr"; this is a comment})
55
+ assert_equal 1, entries.length, 'we should have 1 entry'
56
+ assert_equal 'maiow IN TXT "purr"', entries[0], 'entry should match expected'
57
+ end
58
+
59
+ def test_extract_entry_should_ignore_empty_lines
60
+ entries = DNS::Zone.extract_entries(%Q{\n\nmaiow IN TXT "purr";\n\n})
61
+ assert_equal 1, entries.length, 'we should have 1 entry'
62
+ assert_equal 'maiow IN TXT "purr"', entries[0], 'entry should match expected'
63
+ end
64
+
65
+ def test_extract_entry_using_parentheses_but_not_crossing_line_boundary
66
+ entries = DNS::Zone.extract_entries(%Q{maiow IN TXT ("part1" "part2")})
67
+ assert_equal 1, entries.length, 'we should have 1 entry'
68
+ assert_equal 'maiow IN TXT "part1" "part2"', entries[0], 'entry should match expected'
69
+ end
70
+
71
+ def test_extract_entry_crossing_line_boundary
72
+ entries = DNS::Zone.extract_entries(%Q{maiow1 IN TXT ("part1"\n "part2" )})
73
+ assert_equal 1, entries.length, 'we should have 1 entry'
74
+ assert_equal 'maiow1 IN TXT "part1" "part2"', entries[0], 'entry should match expected'
75
+ end
76
+
77
+ def test_extract_entry_soa_crossing_line_boundary
78
+ entries = DNS::Zone.extract_entries(%Q{
79
+ @ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. (
80
+ 2013101406 ; zone serial number
81
+ 12h ; refresh ttl
82
+ 15m ; retry ttl
83
+ 3w ; expiry ttl
84
+ 3h ; minimum ttl
85
+ )})
86
+ assert_equal 1, entries.length, 'we should have 1 entry'
87
+
88
+ expected_soa = '@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. 2013101406 12h 15m 3w 3h'
89
+ assert_equal expected_soa, entries[0], 'entry should match expected'
90
+ end
91
+
92
+ def test_extract_entries_with_parentheses_crossing_multiple_line_boundaries
93
+ entries = DNS::Zone.extract_entries(%Q{maiow1 IN TXT (\n"part1"\n "part2"\n)})
94
+ assert_equal 1, entries.length, 'we should have 1 entry'
95
+ assert_equal 'maiow1 IN TXT "part1" "part2"', entries[0], 'entry should match expected'
96
+ end
97
+
98
+ def test_extract_entries_with_legal_but_crazy_parentheses_used
99
+ entries = DNS::Zone.extract_entries(%Q{maiow IN TXT (\n(\n("part1")\n \n("part2" \n("part3"\n)\n)\n)\n)})
100
+ assert_equal 1, entries.length, 'we should have 1 entry'
101
+ assert_equal 'maiow IN TXT "part1" "part2" "part3"', entries[0], 'entry should match expected'
102
+ end
103
+
104
+ def test_extract_entry_with_parentheses_in_character_string
105
+ entries = DNS::Zone.extract_entries(%Q{maiow IN TXT ("purr((maiow)")})
106
+ assert_equal 1, entries.length, 'we should have 1 entry'
107
+ assert_equal 'maiow IN TXT "purr((maiow)"', entries[0], 'entry should match expected'
108
+ end
109
+
110
+
111
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dns-zone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0.alpha
5
+ platform: ruby
6
+ authors:
7
+ - Luke Antins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: inch
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard-bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: |
126
+ A Ruby library for building and parsing DNS zone files for use with
127
+ Bind and PowerDNS (with Bind backend) DNS servers.
128
+ email:
129
+ - luke@lividpenguin.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - Gemfile
135
+ - Guardfile
136
+ - HISTORY.md
137
+ - README.md
138
+ - Rakefile
139
+ - dns-zone.gemspec
140
+ - lib/dns/zone.rb
141
+ - lib/dns/zone/rr.rb
142
+ - lib/dns/zone/rr/a.rb
143
+ - lib/dns/zone/rr/aaaa.rb
144
+ - lib/dns/zone/rr/cname.rb
145
+ - lib/dns/zone/rr/mx.rb
146
+ - lib/dns/zone/rr/ns.rb
147
+ - lib/dns/zone/rr/record.rb
148
+ - lib/dns/zone/rr/soa.rb
149
+ - lib/dns/zone/rr/srv.rb
150
+ - lib/dns/zone/rr/txt.rb
151
+ - lib/dns/zone/test_case.rb
152
+ - lib/dns/zone/version.rb
153
+ - test/rr/a_test.rb
154
+ - test/rr/aaaa_test.rb
155
+ - test/rr/cname_test.rb
156
+ - test/rr/mx_test.rb
157
+ - test/rr/ns_test.rb
158
+ - test/rr/record_test.rb
159
+ - test/rr/soa_test.rb
160
+ - test/rr/srv_test.rb
161
+ - test/rr/txt_test.rb
162
+ - test/rr_test.rb
163
+ - test/version_test.rb
164
+ - test/zone_test.rb
165
+ homepage: https://github.com/lantins/dns-zone
166
+ licenses:
167
+ - MIT
168
+ metadata: {}
169
+ post_install_message:
170
+ rdoc_options: []
171
+ require_paths:
172
+ - lib
173
+ required_ruby_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '1.9'
178
+ required_rubygems_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">"
181
+ - !ruby/object:Gem::Version
182
+ version: 1.3.1
183
+ requirements: []
184
+ rubyforge_project:
185
+ rubygems_version: 2.2.0
186
+ signing_key:
187
+ specification_version: 4
188
+ summary: A Ruby library for building and parsing DNS zone files.
189
+ test_files: []
190
+ has_rdoc: