aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Cooper <josh@puppet.com>2018-07-09 17:15:39 -0700
committerJosh Cooper <josh@puppet.com>2018-07-09 19:44:47 -0700
commit2ac89832b7e065df4490d81b3080b2b570a172ad (patch)
treef9f4f28f30a77c958e8e850acf954336b2301dc9
parent1c6ae8bfde5175c5d3512f5acb5352fda94a4801 (diff)
downloadpuppet-hosts_core-2ac89832b7e065df4490d81b3080b2b570a172ad.tar.gz
puppet-hosts_core-2ac89832b7e065df4490d81b3080b2b570a172ad.tar.bz2
Initial host import from puppet#ee7cf4d28077be7d1bdbbe934ea012d41d33deff
-rw-r--r--lib/puppet/provider/host/parsed.rb46
-rw-r--r--lib/puppet/type/host.rb95
-rw-r--r--spec/acceptance/tests/pup_2289_should_not_destroy_data_when_malformed.rb32
-rw-r--r--spec/acceptance/tests/should_create.rb23
-rw-r--r--spec/acceptance/tests/should_create_aliases.rb24
-rw-r--r--spec/acceptance/tests/should_destroy.rb27
-rw-r--r--spec/acceptance/tests/should_modify_alias.rb27
-rw-r--r--spec/acceptance/tests/should_modify_ip.rb27
-rw-r--r--spec/acceptance/tests/should_not_create_existing.rb24
-rw-r--r--spec/acceptance/tests/should_query_all.rb34
-rw-r--r--spec/acceptance/tests/ticket_4131_should_not_create_without_ip.rb29
-rw-r--r--spec/fixtures/unit/provider/host/parsed/valid_hosts19
-rw-r--r--spec/lib/puppet_spec/files.rb108
-rw-r--r--spec/shared_behaviours/all_parsedfile_providers.rb21
-rw-r--r--spec/unit/provider/host/parsed_spec.rb233
-rw-r--r--spec/unit/type/host_spec.rb681
16 files changed, 1450 insertions, 0 deletions
diff --git a/lib/puppet/provider/host/parsed.rb b/lib/puppet/provider/host/parsed.rb
new file mode 100644
index 0000000..d3d0039
--- /dev/null
+++ b/lib/puppet/provider/host/parsed.rb
@@ -0,0 +1,46 @@
+require 'puppet/provider/parsedfile'
+
+hosts = nil
+case Facter.value(:osfamily)
+when "Solaris"; hosts = "/etc/inet/hosts"
+when "windows"
+ require 'win32/resolv'
+ hosts = Win32::Resolv.get_hosts_path
+else
+ hosts = "/etc/hosts"
+end
+
+
+Puppet::Type.type(:host).provide(:parsed,:parent => Puppet::Provider::ParsedFile,
+ :default_target => hosts,:filetype => :flat) do
+ confine :exists => hosts
+
+ text_line :comment, :match => /^#/
+ text_line :blank, :match => /^\s*$/
+ hosts_pattern = '^([0-9a-f:]\S+)\s+([^#\s+]\S+)\s*(.*?)?(?:\s*#\s*(.*))?$'
+ record_line :parsed, :fields => %w{ip name host_aliases comment},
+ :optional => %w{host_aliases comment},
+ :match => /#{hosts_pattern}/,
+ :post_parse => proc { |hash|
+ # An absent comment should match "comment => ''"
+ hash[:comment] = '' if hash[:comment].nil? or hash[:comment] == :absent
+ unless hash[:host_aliases].nil? or hash[:host_aliases] == :absent
+ hash[:host_aliases].gsub!(/\s+/,' ') # Change delimiter
+ end
+ },
+ :to_line => proc { |hash|
+ [:ip, :name].each do |n|
+ raise ArgumentError, _("%{attr} is a required attribute for hosts") % { attr: n } unless hash[n] and hash[n] != :absent
+ end
+ str = "#{hash[:ip]}\t#{hash[:name]}"
+ if hash.include? :host_aliases and !hash[:host_aliases].nil? and hash[:host_aliases] != :absent
+ str += "\t#{hash[:host_aliases]}"
+ end
+ if hash.include? :comment and !hash[:comment].empty?
+ str += "\t# #{hash[:comment]}"
+ end
+ str
+ }
+
+ text_line :incomplete, :match => /(?! (#{hosts_pattern}))/
+end
diff --git a/lib/puppet/type/host.rb b/lib/puppet/type/host.rb
new file mode 100644
index 0000000..3bfcc7e
--- /dev/null
+++ b/lib/puppet/type/host.rb
@@ -0,0 +1,95 @@
+require 'puppet/property/ordered_list'
+
+module Puppet
+ Type.newtype(:host) do
+ ensurable
+
+ newproperty(:ip) do
+ desc "The host's IP address, IPv4 or IPv6."
+
+
+ def valid_v4?(addr)
+ if /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ =~ addr
+ return $~.captures.all? {|i| i = i.to_i; i >= 0 and i <= 255 }
+ end
+ return false
+ end
+
+ def valid_v6?(addr)
+ # http://forums.dartware.com/viewtopic.php?t=452
+ # ...and, yes, it is this hard. Doing it programmatically is harder.
+ return true if addr =~ /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
+
+ return false
+ end
+ def valid_newline?(addr)
+ return false if (addr =~ /\n/ || addr =~ /\r/)
+ return true
+ end
+
+ validate do |value|
+ return true if ((valid_v4?(value) || valid_v6?(value)) && (valid_newline?(value)))
+ raise Puppet::Error, _("Invalid IP address %{value}") % { value: value.inspect }
+ end
+ end
+
+ # for now we use OrderedList to indicate that the order does matter.
+ newproperty(:host_aliases, :parent => Puppet::Property::OrderedList) do
+ desc "Any aliases the host might have. Multiple values must be
+ specified as an array."
+
+ def delimiter
+ " "
+ end
+
+ def inclusive?
+ true
+ end
+
+ validate do |value|
+ # This regex already includes newline check.
+ raise Puppet::Error, _("Host aliases cannot include whitespace") if value =~ /\s/
+ raise Puppet::Error, _("Host aliases cannot be an empty string. Use an empty array to delete all host_aliases ") if value =~ /^\s*$/
+ end
+
+ end
+
+ newproperty(:comment) do
+ desc "A comment that will be attached to the line with a # character."
+ validate do |value|
+ raise Puppet::Error, _("Comment cannot include newline") if (value =~ /\n/ || value =~ /\r/)
+ end
+ end
+
+ newproperty(:target) do
+ desc "The file in which to store service information. Only used by
+ those providers that write to disk. On most systems this defaults to `/etc/hosts`."
+
+ defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile)
+ @resource.class.defaultprovider.default_target
+ else
+ nil
+ end
+ }
+ end
+
+ newparam(:name) do
+ desc "The host name."
+
+ isnamevar
+
+ validate do |value|
+ value.split('.').each do |hostpart|
+ unless hostpart =~ /^([\w]+|[\w][\w\-]+[\w])$/
+ raise Puppet::Error, _("Invalid host name")
+ end
+ end
+ raise Puppet::Error, _("Hostname cannot include newline") if (value =~ /\n/ || value =~ /\r/)
+ end
+ end
+
+ @doc = "Installs and manages host entries. For most systems, these
+ entries will just be in `/etc/hosts`, but some systems (notably OS X)
+ will have different solutions."
+ end
+end
diff --git a/spec/acceptance/tests/pup_2289_should_not_destroy_data_when_malformed.rb b/spec/acceptance/tests/pup_2289_should_not_destroy_data_when_malformed.rb
new file mode 100644
index 0000000..670651d
--- /dev/null
+++ b/spec/acceptance/tests/pup_2289_should_not_destroy_data_when_malformed.rb
@@ -0,0 +1,32 @@
+test_name "should not delete data when existing content is malformed"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ file = agent.tmpfile('host-not-delete-data')
+
+ teardown do
+ on(agent, "rm -f #{file}", :acceptable_exit_codes => (0..255))
+ end
+
+ step "(setup) populate test file with host information"
+ on(agent, "printf '127.0.0.2 existing alias\n' > #{file}")
+
+ step "(setup) populate test file with a malformed line"
+ on(agent, "printf '==\n' >> #{file}")
+
+ step "tell puppet to add another host entry"
+ on(agent, puppet_resource('host', 'test', "target=#{file}",
+ 'ensure=present', 'ip=127.0.0.3', 'host_aliases=foo'))
+
+ step "verify that the initial host entry was not deleted"
+ on(agent, "cat #{file}") do |res|
+ fail_test "existing host data was deleted" unless
+ res.stdout.include? 'existing'
+ end
+
+end
diff --git a/spec/acceptance/tests/should_create.rb b/spec/acceptance/tests/should_create.rb
new file mode 100644
index 0000000..b590f85
--- /dev/null
+++ b/spec/acceptance/tests/should_create.rb
@@ -0,0 +1,23 @@
+test_name "host should create"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ target = agent.tmpfile('host-create')
+
+ step "clean up for the test"
+ on agent, "rm -f #{target}"
+
+ step "create the host record"
+ on(agent, puppet_resource("host", "test", "ensure=present",
+ "ip=127.0.0.1", "target=#{target}"))
+
+ step "verify that the record was created"
+ on(agent, "cat #{target} ; rm -f #{target}") do
+ fail_test "record was not present" unless stdout =~ /^127\.0\.0\.1[[:space:]]+test/
+ end
+end
diff --git a/spec/acceptance/tests/should_create_aliases.rb b/spec/acceptance/tests/should_create_aliases.rb
new file mode 100644
index 0000000..be4a134
--- /dev/null
+++ b/spec/acceptance/tests/should_create_aliases.rb
@@ -0,0 +1,24 @@
+test_name "host should create aliases"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ target = agent.tmpfile('host-create-aliases')
+
+ step "clean up the system for testing"
+ on agent, "rm -f #{target}"
+
+ step "create the record"
+ on(agent, puppet_resource('host', 'test', "ensure=present",
+ "ip=127.0.0.7", "target=#{target}", "host_aliases=alias"))
+
+ step "verify that the aliases were added"
+ on(agent, "cat #{target} ; rm -f #{target}") do
+ fail_test "alias was missing" unless
+ stdout =~ /^127\.0\.0\.7[[:space:]]+test[[:space:]]alias/
+ end
+end
diff --git a/spec/acceptance/tests/should_destroy.rb b/spec/acceptance/tests/should_destroy.rb
new file mode 100644
index 0000000..97f1477
--- /dev/null
+++ b/spec/acceptance/tests/should_destroy.rb
@@ -0,0 +1,27 @@
+test_name "should be able to remove a host record"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ file = agent.tmpfile('host-destroy')
+ line = "127.0.0.7 test1"
+
+ step "set up files for the test"
+ on agent, "printf '#{line}\n' > #{file}"
+
+ step "delete the resource from the file"
+ on(agent, puppet_resource('host', 'test1', "target=#{file}",
+ 'ensure=absent', 'ip=127.0.0.7'))
+
+ step "verify that the content was removed"
+ on(agent, "cat #{file}; rm -f #{file}") do
+ fail_test "the content was still present" if stdout.include? line
+ end
+
+ step "clean up after the test"
+ on agent, "rm -f #{file}"
+end
diff --git a/spec/acceptance/tests/should_modify_alias.rb b/spec/acceptance/tests/should_modify_alias.rb
new file mode 100644
index 0000000..07198b1
--- /dev/null
+++ b/spec/acceptance/tests/should_modify_alias.rb
@@ -0,0 +1,27 @@
+test_name "should be able to modify a host alias"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ file = agent.tmpfile('host-modify-alias')
+
+ step "set up files for the test"
+ on agent, "printf '127.0.0.8 test alias\n' > #{file}"
+
+ step "modify the resource"
+ on(agent, puppet_resource('host', 'test', "target=#{file}",
+ 'ensure=present', 'ip=127.0.0.8', 'host_aliases=banzai'))
+
+ step "verify that the content was updated"
+ on(agent, "cat #{file}; rm -f #{file}") do
+ fail_test "the alias was not updated" unless
+ stdout =~ /^127\.0\.0\.8[[:space:]]+test[[:space:]]+banzai[[:space:]]*$/
+ end
+
+ step "clean up after the test"
+ on agent, "rm -f #{file}"
+end
diff --git a/spec/acceptance/tests/should_modify_ip.rb b/spec/acceptance/tests/should_modify_ip.rb
new file mode 100644
index 0000000..10c5adf
--- /dev/null
+++ b/spec/acceptance/tests/should_modify_ip.rb
@@ -0,0 +1,27 @@
+test_name "should be able to modify a host address"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ file = agent.tmpfile('host-modify-ip')
+
+ step "set up files for the test"
+ on agent, "printf '127.0.0.9 test alias\n' > #{file}"
+
+ step "modify the resource"
+ on(agent, puppet_resource('host', 'test', "target=#{file}",
+ 'ensure=present', 'ip=127.0.0.10', 'host_aliases=alias'))
+
+ step "verify that the content was updated"
+ on(agent, "cat #{file}; rm -f #{file}") do
+ fail_test "the address was not updated" unless
+ stdout =~ /^127\.0\.0\.10[[:space:]]+test[[:space:]]+alias[[:space:]]*$/
+ end
+
+ step "clean up after the test"
+ on agent, "rm -f #{file}"
+end
diff --git a/spec/acceptance/tests/should_not_create_existing.rb b/spec/acceptance/tests/should_not_create_existing.rb
new file mode 100644
index 0000000..0cdc87b
--- /dev/null
+++ b/spec/acceptance/tests/should_not_create_existing.rb
@@ -0,0 +1,24 @@
+test_name "should not create host if it exists"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ file = agent.tmpfile('host-not-create-existing')
+
+ step "set up the system for the test"
+ on agent, "printf '127.0.0.2 test alias\n' > #{file}"
+
+ step "tell puppet to ensure the host exists"
+ on(agent, puppet_resource('host', 'test', "target=#{file}",
+ 'ensure=present', 'ip=127.0.0.2', 'host_aliases=alias')) do
+ fail_test "darn, we created the host record" if
+ stdout.include? '/Host[test1]/ensure: created'
+ end
+
+ step "clean up after we created things"
+ on agent, "rm -f #{file}"
+end
diff --git a/spec/acceptance/tests/should_query_all.rb b/spec/acceptance/tests/should_query_all.rb
new file mode 100644
index 0000000..e60756d
--- /dev/null
+++ b/spec/acceptance/tests/should_query_all.rb
@@ -0,0 +1,34 @@
+test_name "should query all hosts from hosts file"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+content = %q{127.0.0.1 test1 test1.local
+127.0.0.2 test2 test2.local
+127.0.0.3 test3 test3.local
+127.0.0.4 test4 test4.local
+}
+
+agents.each do |agent|
+ backup = agent.tmpfile('host-query-all')
+
+ step "configure the system for testing (including file backups)"
+ on agent, "cp /etc/hosts #{backup}"
+ on agent, "cat > /etc/hosts", :stdin => content
+
+ step "query all host records using puppet"
+ on(agent, puppet_resource('host')) do
+ found = stdout.scan(/host \{ '([^']+)'/).flatten.sort
+ fail_test "the list of returned hosts was wrong: #{found.join(', ')}" unless
+ found == %w{test1 test2 test3 test4}
+
+ count = stdout.scan(/ensure\s+=>\s+'present'/).length
+ fail_test "found #{count} records, wanted 4" unless count == 4
+ end
+
+ step "clean up the system afterwards"
+ on agent, "cat #{backup} > /etc/hosts && rm -f #{backup}"
+end
diff --git a/spec/acceptance/tests/ticket_4131_should_not_create_without_ip.rb b/spec/acceptance/tests/ticket_4131_should_not_create_without_ip.rb
new file mode 100644
index 0000000..867ff6c
--- /dev/null
+++ b/spec/acceptance/tests/ticket_4131_should_not_create_without_ip.rb
@@ -0,0 +1,29 @@
+test_name "#4131: should not create host without IP attribute"
+
+tag 'audit:low',
+ 'audit:refactor', # Use block style `test_name`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+agents.each do |agent|
+ file = agent.tmpfile('4131-require-ip')
+
+ step "configure the target system for the test"
+ on agent, "rm -rf #{file} ; touch #{file}"
+
+ step "try to create the host, which should fail"
+ # REVISIT: This step should properly need to handle the non-zero exit code,
+ # and #5668 has been filed to record that. When it is fixed this test will
+ # start to fail, and this comment will tell you why. --daniel 2010-12-24
+ on(agent, puppet_resource('host', 'test', "target=#{file}",
+ "host_aliases=alias")) do
+ fail_test "puppet didn't complain about the missing attribute" unless
+ stderr.include? 'ip is a required attribute for hosts' unless agent['locale'] == 'ja'
+ end
+
+ step "verify that the host was not added to the file"
+ on(agent, "cat #{file} ; rm -f #{file}") do
+ fail_test "the host was apparently added to the file" if stdout.include? 'test'
+ end
+end
diff --git a/spec/fixtures/unit/provider/host/parsed/valid_hosts b/spec/fixtures/unit/provider/host/parsed/valid_hosts
new file mode 100644
index 0000000..2463629
--- /dev/null
+++ b/spec/fixtures/unit/provider/host/parsed/valid_hosts
@@ -0,0 +1,19 @@
+# Some leading comment, that should be ignored
+# The next line is empty so it should be ignored
+
+::1 localhost
+
+# We now try another delimiter: Several tabs
+127.0.0.1 localhost
+
+# No test trailing spaces
+10.0.0.1 host1
+
+# Ok its time to test aliases
+2001:252:0:1::2008:8 ipv6host alias1
+192.168.0.1 ipv4host alias2 alias3
+
+# Testing inlinecomments now
+192.168.0.2 host3 # This is host3
+192.168.0.3 host4 alias10 # This is host4
+192.168.0.4 host5 alias11 alias12 # This is host5
diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb
new file mode 100644
index 0000000..b34daed
--- /dev/null
+++ b/spec/lib/puppet_spec/files.rb
@@ -0,0 +1,108 @@
+require 'fileutils'
+require 'tempfile'
+require 'tmpdir'
+require 'pathname'
+
+# A support module for testing files.
+module PuppetSpec::Files
+ def self.cleanup
+ $global_tempfiles ||= []
+ while path = $global_tempfiles.pop do
+ begin
+ Dir.unstub(:entries)
+ FileUtils.rm_rf path, :secure => true
+ rescue Errno::ENOENT
+ # nothing to do
+ end
+ end
+ end
+
+ module_function
+
+ def make_absolute(path)
+ path = File.expand_path(path)
+ path[0] = 'c' if Puppet.features.microsoft_windows?
+ path
+ end
+
+ def tmpfile(name, dir = nil)
+ dir ||= Dir.tmpdir
+ path = Puppet::FileSystem.expand_path(make_tmpname(name, nil).encode(Encoding::UTF_8), dir)
+ record_tmp(File.expand_path(path))
+
+ path
+ end
+
+ def file_containing(name, contents)
+ file = tmpfile(name)
+ File.open(file, 'wb') { |f| f.write(contents) }
+ file
+ end
+
+ def script_containing(name, contents)
+ file = tmpfile(name)
+ if Puppet.features.microsoft_windows?
+ file += '.bat'
+ text = contents[:windows]
+ else
+ text = contents[:posix]
+ end
+ File.open(file, 'wb') { |f| f.write(text) }
+ Puppet::FileSystem.chmod(0755, file)
+ file
+ end
+
+ def tmpdir(name)
+ dir = Puppet::FileSystem.expand_path(Dir.mktmpdir(name).encode!(Encoding::UTF_8))
+
+ record_tmp(dir)
+
+ dir
+ end
+
+ # Copied from ruby 2.4 source
+ def make_tmpname((prefix, suffix), n)
+ prefix = (String.try_convert(prefix) or
+ raise ArgumentError, "unexpected prefix: #{prefix.inspect}")
+ suffix &&= (String.try_convert(suffix) or
+ raise ArgumentError, "unexpected suffix: #{suffix.inspect}")
+ t = Time.now.strftime("%Y%m%d")
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}".dup
+ path << "-#{n}" if n
+ path << suffix if suffix
+ path
+ end
+
+ def dir_containing(name, contents_hash)
+ dir_contained_in(tmpdir(name), contents_hash)
+ end
+
+ def dir_contained_in(dir, contents_hash)
+ contents_hash.each do |k,v|
+ if v.is_a?(Hash)
+ Dir.mkdir(tmp = File.join(dir,k))
+ dir_contained_in(tmp, v)
+ else
+ file = File.join(dir, k)
+ File.open(file, 'wb') {|f| f.write(v) }
+ end
+ end
+ dir
+ end
+
+ def record_tmp(tmp)
+ # ...record it for cleanup,
+ $global_tempfiles ||= []
+ $global_tempfiles << tmp
+ end
+
+ def expect_file_mode(file, mode)
+ actual_mode = "%o" % Puppet::FileSystem.stat(file).mode
+ target_mode = if Puppet.features.microsoft_windows?
+ mode
+ else
+ "10" + "%04i" % mode.to_i
+ end
+ expect(actual_mode).to eq(target_mode)
+ end
+end
diff --git a/spec/shared_behaviours/all_parsedfile_providers.rb b/spec/shared_behaviours/all_parsedfile_providers.rb
new file mode 100644
index 0000000..9fdf54b
--- /dev/null
+++ b/spec/shared_behaviours/all_parsedfile_providers.rb
@@ -0,0 +1,21 @@
+shared_examples_for "all parsedfile providers" do |provider, *files|
+ if files.empty? then
+ files = my_fixtures
+ end
+
+ files.flatten.each do |file|
+ it "should rewrite #{file} reasonably unchanged" do
+ provider.stubs(:default_target).returns(file)
+ provider.prefetch
+
+ text = provider.to_file(provider.target_records(file))
+ text.gsub!(/^# HEADER.+\n/, '')
+
+ oldlines = File.readlines(file)
+ newlines = text.chomp.split "\n"
+ oldlines.zip(newlines).each do |old, new|
+ expect(new.gsub(/\s+/, '')).to eq(old.chomp.gsub(/\s+/, ''))
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/host/parsed_spec.rb b/spec/unit/provider/host/parsed_spec.rb
new file mode 100644
index 0000000..b2cba76
--- /dev/null
+++ b/spec/unit/provider/host/parsed_spec.rb
@@ -0,0 +1,233 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'shared_behaviours/all_parsedfile_providers'
+
+require 'puppet_spec/files'
+
+provider_class = Puppet::Type.type(:host).provider(:parsed)
+
+describe provider_class do
+ include PuppetSpec::Files
+
+ before do
+ @host_class = Puppet::Type.type(:host)
+ @provider = @host_class.provider(:parsed)
+ @hostfile = tmpfile('hosts')
+ @provider.any_instance.stubs(:target).returns @hostfile
+ end
+
+ after :each do
+ @provider.initvars
+ end
+
+ def mkhost(args)
+ hostresource = Puppet::Type::Host.new(:name => args[:name])
+ hostresource.stubs(:should).with(:target).returns @hostfile
+
+ # Using setters of provider to build our testobject
+ # Note: We already proved, that in case of host_aliases
+ # the provider setter "host_aliases=(value)" will be
+ # called with the joined array, so we just simulate that
+ host = @provider.new(hostresource)
+ args.each do |property,value|
+ value = value.join(" ") if property == :host_aliases and value.is_a?(Array)
+ host.send("#{property}=", value)
+ end
+ host
+ end
+
+ def genhost(host)
+ @provider.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam)
+ File.stubs(:chown)
+ File.stubs(:chmod)
+ Puppet::Util::SUIDManager.stubs(:asuser).yields
+ host.flush
+ @provider.target_object(@hostfile).read
+ end
+
+ describe "when parsing on incomplete line" do
+
+ it "should work for only ip" do
+ expect(@provider.parse_line("127.0.0.1")[:line]).to eq("127.0.0.1")
+ end
+
+ it "should work for only hostname" do
+ expect(@provider.parse_line("www.example.com")[:line]).to eq("www.example.com")
+ end
+
+ it "should work for ip and space" do
+ expect(@provider.parse_line("127.0.0.1 ")[:line]).to eq("127.0.0.1 ")
+ end
+
+ it "should work for hostname and space" do
+ expect(@provider.parse_line("www.example.com ")[:line]).to eq("www.example.com ")
+ end
+
+ it "should work for hostname and host_aliases" do
+ expect(@provider.parse_line("www.example.com www xyz")[:line]).to eq("www.example.com www xyz")
+ end
+
+ it "should work for ip and comment" do
+ expect(@provider.parse_line("127.0.0.1 #www xyz")[:line]).to eq("127.0.0.1 #www xyz")
+ end
+
+ it "should work for hostname and comment" do
+ expect(@provider.parse_line("xyz #www test123")[:line]).to eq("xyz #www test123")
+ end
+
+ it "should work for crazy incomplete lines" do
+ expect(@provider.parse_line("%th1s is a\t cr$zy !incompl1t line")[:line]).to eq("%th1s is a\t cr$zy !incompl1t line")
+ end
+
+ end
+
+ describe "when parsing a line with ip and hostname" do
+
+ it "should parse an ipv4 from the first field" do
+ expect(@provider.parse_line("127.0.0.1 localhost")[:ip]).to eq("127.0.0.1")
+ end
+
+ it "should parse an ipv6 from the first field" do
+ expect(@provider.parse_line("::1 localhost")[:ip]).to eq("::1")
+ end
+
+ it "should parse the name from the second field" do
+ expect(@provider.parse_line("::1 localhost")[:name]).to eq("localhost")
+ end
+
+ it "should set an empty comment" do
+ expect(@provider.parse_line("::1 localhost")[:comment]).to eq("")
+ end
+
+ it "should set host_aliases to :absent" do
+ expect(@provider.parse_line("::1 localhost")[:host_aliases]).to eq(:absent)
+ end
+
+ end
+
+ describe "when parsing a line with ip, hostname and comment" do
+ before do
+ @testline = "127.0.0.1 localhost # A comment with a #-char"
+ end
+
+ it "should parse the ip from the first field" do
+ expect(@provider.parse_line(@testline)[:ip]).to eq("127.0.0.1")
+ end
+
+ it "should parse the hostname from the second field" do
+ expect(@provider.parse_line(@testline)[:name]).to eq("localhost")
+ end
+
+ it "should parse the comment after the first '#' character" do
+ expect(@provider.parse_line(@testline)[:comment]).to eq('A comment with a #-char')
+ end
+
+ end
+
+ describe "when parsing a line with ip, hostname and aliases" do
+
+ it "should parse alias from the third field" do
+ expect(@provider.parse_line("127.0.0.1 localhost localhost.localdomain")[:host_aliases]).to eq("localhost.localdomain")
+ end
+
+ it "should parse multiple aliases" do
+ expect(@provider.parse_line("127.0.0.1 host alias1 alias2")[:host_aliases]).to eq('alias1 alias2')
+ expect(@provider.parse_line("127.0.0.1 host alias1\talias2")[:host_aliases]).to eq('alias1 alias2')
+ expect(@provider.parse_line("127.0.0.1 host alias1\talias2 alias3")[:host_aliases]).to eq('alias1 alias2 alias3')
+ end
+
+ end
+
+ describe "when parsing a line with ip, hostname, aliases and comment" do
+
+ before do
+ # Just playing with a few different delimiters
+ @testline = "127.0.0.1\t host alias1\talias2 alias3 # A comment with a #-char"
+ end
+
+ it "should parse the ip from the first field" do
+ expect(@provider.parse_line(@testline)[:ip]).to eq("127.0.0.1")
+ end
+
+ it "should parse the hostname from the second field" do
+ expect(@provider.parse_line(@testline)[:name]).to eq("host")
+ end
+
+ it "should parse all host_aliases from the third field" do
+ expect(@provider.parse_line(@testline)[:host_aliases]).to eq('alias1 alias2 alias3')
+ end
+
+ it "should parse the comment after the first '#' character" do
+ expect(@provider.parse_line(@testline)[:comment]).to eq('A comment with a #-char')
+ end
+
+ end
+
+ describe "when operating on /etc/hosts like files" do
+ it_should_behave_like "all parsedfile providers",
+ provider_class, my_fixtures('valid*')
+
+ it "should be able to generate a simple hostfile entry" do
+ host = mkhost(
+ :name => 'localhost',
+ :ip => '127.0.0.1',
+ :ensure => :present
+ )
+ expect(genhost(host)).to eq("127.0.0.1\tlocalhost\n")
+ end
+
+ it "should be able to generate an entry with one alias" do
+ host = mkhost(
+ :name => 'localhost.localdomain',
+ :ip => '127.0.0.1',
+ :host_aliases => 'localhost',
+ :ensure => :present
+ )
+ expect(genhost(host)).to eq("127.0.0.1\tlocalhost.localdomain\tlocalhost\n")
+ end
+
+ it "should be able to generate an entry with more than one alias" do
+ host = mkhost(
+ :name => 'host',
+ :ip => '192.0.0.1',
+ :host_aliases => [ 'a1','a2','a3','a4' ],
+ :ensure => :present
+ )
+ expect(genhost(host)).to eq("192.0.0.1\thost\ta1 a2 a3 a4\n")
+ end
+
+ it "should be able to generate a simple hostfile entry with comments" do
+ host = mkhost(
+ :name => 'localhost',
+ :ip => '127.0.0.1',
+ :comment => 'Bazinga!',
+ :ensure => :present
+ )
+ expect(genhost(host)).to eq("127.0.0.1\tlocalhost\t# Bazinga!\n")
+ end
+
+ it "should be able to generate an entry with one alias and a comment" do
+ host = mkhost(
+ :name => 'localhost.localdomain',
+ :ip => '127.0.0.1',
+ :host_aliases => 'localhost',
+ :comment => 'Bazinga!',
+ :ensure => :present
+ )
+ expect(genhost(host)).to eq("127.0.0.1\tlocalhost.localdomain\tlocalhost\t# Bazinga!\n")
+ end
+
+ it "should be able to generate an entry with more than one alias and a comment" do
+ host = mkhost(
+ :name => 'host',
+ :ip => '192.0.0.1',
+ :host_aliases => [ 'a1','a2','a3','a4' ],
+ :comment => 'Bazinga!',
+ :ensure => :present
+ )
+ expect(genhost(host)).to eq("192.0.0.1\thost\ta1 a2 a3 a4\t# Bazinga!\n")
+ end
+
+ end
+
+end
diff --git a/spec/unit/type/host_spec.rb b/spec/unit/type/host_spec.rb
new file mode 100644
index 0000000..ecce6e2
--- /dev/null
+++ b/spec/unit/type/host_spec.rb
@@ -0,0 +1,681 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+host = Puppet::Type.type(:host)
+
+describe host do
+ FakeHostProvider = Struct.new(:ip, :host_aliases, :comment)
+ before do
+ @class = host
+ @catalog = Puppet::Resource::Catalog.new
+ @provider = FakeHostProvider.new
+ @resource = stub 'resource', :resource => nil, :provider => @provider
+ end
+
+ it "should have :name be its namevar" do
+ expect(@class.key_attributes).to eq([:name])
+ end
+
+ describe "when validating attributes" do
+ [:name, :provider ].each do |param|
+ it "should have a #{param} parameter" do
+ expect(@class.attrtype(param)).to eq(:param)
+ end
+ end
+
+ [:ip, :target, :host_aliases, :comment, :ensure].each do |property|
+ it "should have a #{property} property" do
+ expect(@class.attrtype(property)).to eq(:property)
+ end
+ end
+
+ it "should have a list host_aliases" do
+ expect(@class.attrclass(:host_aliases).ancestors).to be_include(Puppet::Property::OrderedList)
+ end
+
+ end
+
+ describe "when validating values" do
+ it "should support present as a value for ensure" do
+ expect { @class.new(:name => "foo", :ensure => :present) }.not_to raise_error
+ end
+
+ it "should support absent as a value for ensure" do
+ expect { @class.new(:name => "foo", :ensure => :absent) }.not_to raise_error
+ end
+
+ it "should accept IPv4 addresses" do
+ expect { @class.new(:name => "foo", :ip => '10.96.0.1') }.not_to raise_error
+ end
+
+ it "should accept long IPv6 addresses" do
+ # Taken from wikipedia article about ipv6
+ expect { @class.new(:name => "foo", :ip => '2001:0db8:85a3:08d3:1319:8a2e:0370:7344') }.not_to raise_error
+ end
+
+ it "should accept one host_alias" do
+ expect { @class.new(:name => "foo", :host_aliases => 'alias1') }.not_to raise_error
+ end
+
+ it "should accept multiple host_aliases" do
+ expect { @class.new(:name => "foo", :host_aliases => [ 'alias1', 'alias2' ]) }.not_to raise_error
+ end
+
+ it "should accept shortened IPv6 addresses" do
+ expect { @class.new(:name => "foo", :ip => '2001:db8:0:8d3:0:8a2e:70:7344') }.not_to raise_error
+ expect { @class.new(:name => "foo", :ip => '::ffff:192.0.2.128') }.not_to raise_error
+ expect { @class.new(:name => "foo", :ip => '::1') }.not_to raise_error
+ end
+
+ it "should not accept malformed IPv4 addresses like 192.168.0.300" do
+ expect { @class.new(:name => "foo", :ip => '192.168.0.300') }.to raise_error(Puppet::ResourceError, /Parameter ip failed/)
+ end
+
+ it "should reject over-long IPv4 addresses" do
+ expect { @class.new(:name => "foo", :ip => '10.10.10.10.10') }.to raise_error(Puppet::ResourceError, /Parameter ip failed/)
+ end
+
+ it "should not accept malformed IP addresses like 2001:0dg8:85a3:08d3:1319:8a2e:0370:7344" do
+ expect { @class.new(:name => "foo", :ip => '2001:0dg8:85a3:08d3:1319:8a2e:0370:7344') }.to raise_error(Puppet::ResourceError, /Parameter ip failed/)
+ end
+
+ # Assorted, annotated IPv6 passes.
+ ["::1", # loopback, compressed, non-routable
+ "::", # unspecified, compressed, non-routable
+ "0:0:0:0:0:0:0:1", # loopback, full
+ "0:0:0:0:0:0:0:0", # unspecified, full
+ "2001:DB8:0:0:8:800:200C:417A", # unicast, full
+ "FF01:0:0:0:0:0:0:101", # multicast, full
+ "2001:DB8::8:800:200C:417A", # unicast, compressed
+ "FF01::101", # multicast, compressed
+ # Some more test cases that should pass.
+ "2001:0000:1234:0000:0000:C1C0:ABCD:0876",
+ "3ffe:0b00:0000:0000:0001:0000:0000:000a",
+ "FF02:0000:0000:0000:0000:0000:0000:0001",
+ "0000:0000:0000:0000:0000:0000:0000:0001",
+ "0000:0000:0000:0000:0000:0000:0000:0000",
+ # Assorted valid, compressed IPv6 addresses.
+ "2::10",
+ "ff02::1",
+ "fe80::",
+ "2002::",
+ "2001:db8::",
+ "2001:0db8:1234::",
+ "::ffff:0:0",
+ "::1",
+ "1:2:3:4:5:6:7:8",
+ "1:2:3:4:5:6::8",
+ "1:2:3:4:5::8",
+ "1:2:3:4::8",
+ "1:2:3::8",
+ "1:2::8",
+ "1::8",
+ "1::2:3:4:5:6:7",
+ "1::2:3:4:5:6",
+ "1::2:3:4:5",
+ "1::2:3:4",
+ "1::2:3",
+ "1::8",
+ "::2:3:4:5:6:7:8",
+ "::2:3:4:5:6:7",
+ "::2:3:4:5:6",
+ "::2:3:4:5",
+ "::2:3:4",
+ "::2:3",
+ "::8",
+ "1:2:3:4:5:6::",
+ "1:2:3:4:5::",
+ "1:2:3:4::",
+ "1:2:3::",
+ "1:2::",
+ "1::",
+ "1:2:3:4:5::7:8",
+ "1:2:3:4::7:8",
+ "1:2:3::7:8",
+ "1:2::7:8",
+ "1::7:8",
+ # IPv4 addresses as dotted-quads
+ "1:2:3:4:5:6:1.2.3.4",
+ "1:2:3:4:5::1.2.3.4",
+ "1:2:3:4::1.2.3.4",
+ "1:2:3::1.2.3.4",
+ "1:2::1.2.3.4",
+ "1::1.2.3.4",
+ "1:2:3:4::5:1.2.3.4",
+ "1:2:3::5:1.2.3.4",
+ "1:2::5:1.2.3.4",
+ "1::5:1.2.3.4",
+ "1::5:11.22.33.44",
+ "fe80::217:f2ff:254.7.237.98",
+ "::ffff:192.168.1.26",
+ "::ffff:192.168.1.1",
+ "0:0:0:0:0:0:13.1.68.3", # IPv4-compatible IPv6 address, full, deprecated
+ "0:0:0:0:0:FFFF:129.144.52.38", # IPv4-mapped IPv6 address, full
+ "::13.1.68.3", # IPv4-compatible IPv6 address, compressed, deprecated
+ "::FFFF:129.144.52.38", # IPv4-mapped IPv6 address, compressed
+ "fe80:0:0:0:204:61ff:254.157.241.86",
+ "fe80::204:61ff:254.157.241.86",
+ "::ffff:12.34.56.78",
+ "::ffff:192.0.2.128", # this is OK, since there's a single zero digit in IPv4
+ "fe80:0000:0000:0000:0204:61ff:fe9d:f156",
+ "fe80:0:0:0:204:61ff:fe9d:f156",
+ "fe80::204:61ff:fe9d:f156",
+ "::1",
+ "fe80::",
+ "fe80::1",
+ "::ffff:c000:280",
+
+ # Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ "2001:db8:85a3:0:0:8a2e:370:7334",
+ "2001:db8:85a3::8a2e:370:7334",
+ "2001:0db8:0000:0000:0000:0000:1428:57ab",
+ "2001:0db8:0000:0000:0000::1428:57ab",
+ "2001:0db8:0:0:0:0:1428:57ab",
+ "2001:0db8:0:0::1428:57ab",
+ "2001:0db8::1428:57ab",
+ "2001:db8::1428:57ab",
+ "0000:0000:0000:0000:0000:0000:0000:0001",
+ "::1",
+ "::ffff:0c22:384e",
+ "2001:0db8:1234:0000:0000:0000:0000:0000",
+ "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff",
+ "2001:db8:a::123",
+ "fe80::",
+
+ "1111:2222:3333:4444:5555:6666:7777:8888",
+ "1111:2222:3333:4444:5555:6666:7777::",
+ "1111:2222:3333:4444:5555:6666::",
+ "1111:2222:3333:4444:5555::",
+ "1111:2222:3333:4444::",
+ "1111:2222:3333::",
+ "1111:2222::",
+ "1111::",
+ "1111:2222:3333:4444:5555:6666::8888",
+ "1111:2222:3333:4444:5555::8888",
+ "1111:2222:3333:4444::8888",
+ "1111:2222:3333::8888",
+ "1111:2222::8888",
+ "1111::8888",
+ "::8888",
+ "1111:2222:3333:4444:5555::7777:8888",
+ "1111:2222:3333:4444::7777:8888",
+ "1111:2222:3333::7777:8888",
+ "1111:2222::7777:8888",
+ "1111::7777:8888",
+ "::7777:8888",
+ "1111:2222:3333:4444::6666:7777:8888",
+ "1111:2222:3333::6666:7777:8888",
+ "1111:2222::6666:7777:8888",
+ "1111::6666:7777:8888",
+ "::6666:7777:8888",
+ "1111:2222:3333::5555:6666:7777:8888",
+ "1111:2222::5555:6666:7777:8888",
+ "1111::5555:6666:7777:8888",
+ "::5555:6666:7777:8888",
+ "1111:2222::4444:5555:6666:7777:8888",
+ "1111::4444:5555:6666:7777:8888",
+ "::4444:5555:6666:7777:8888",
+ "1111::3333:4444:5555:6666:7777:8888",
+ "::3333:4444:5555:6666:7777:8888",
+ "::2222:3333:4444:5555:6666:7777:8888",
+ "1111:2222:3333:4444:5555:6666:123.123.123.123",
+ "1111:2222:3333:4444:5555::123.123.123.123",
+ "1111:2222:3333:4444::123.123.123.123",
+ "1111:2222:3333::123.123.123.123",
+ "1111:2222::123.123.123.123",
+ "1111::123.123.123.123",
+ "::123.123.123.123",
+ "1111:2222:3333:4444::6666:123.123.123.123",
+ "1111:2222:3333::6666:123.123.123.123",
+ "1111:2222::6666:123.123.123.123",
+ "1111::6666:123.123.123.123",
+ "::6666:123.123.123.123",
+ "1111:2222:3333::5555:6666:123.123.123.123",
+ "1111:2222::5555:6666:123.123.123.123",
+ "1111::5555:6666:123.123.123.123",
+ "::5555:6666:123.123.123.123",
+ "1111:2222::4444:5555:6666:123.123.123.123",
+ "1111::4444:5555:6666:123.123.123.123",
+ "::4444:5555:6666:123.123.123.123",
+ "1111::3333:4444:5555:6666:123.123.123.123",
+ "::2222:3333:4444:5555:6666:123.123.123.123",
+
+ # Playing with combinations of "0" and "::"; these are all sytactically
+ # correct, but are bad form because "0" adjacent to "::" should be
+ # combined into "::"
+ "::0:0:0:0:0:0:0",
+ "::0:0:0:0:0:0",
+ "::0:0:0:0:0",
+ "::0:0:0:0",
+ "::0:0:0",
+ "::0:0",
+ "::0",
+ "0:0:0:0:0:0:0::",
+ "0:0:0:0:0:0::",
+ "0:0:0:0:0::",
+ "0:0:0:0::",
+ "0:0:0::",
+ "0:0::",
+ "0::",
+
+ # Additional cases: http://crisp.tweakblogs.net/blog/2031/ipv6-validation-%28and-caveats%29.html
+ "0:a:b:c:d:e:f::",
+ "::0:a:b:c:d:e:f", # syntactically correct, but bad form (::0:... could be combined)
+ "a:b:c:d:e:f:0::",
+ ].each do |ip|
+ it "should accept #{ip.inspect} as an IPv6 address" do
+ expect { @class.new(:name => "foo", :ip => ip) }.not_to raise_error
+ end
+ end
+
+ # ...aaaand, some failure cases.
+ [":",
+ "02001:0000:1234:0000:0000:C1C0:ABCD:0876", # extra 0 not allowed!
+ "2001:0000:1234:0000:00001:C1C0:ABCD:0876", # extra 0 not allowed!
+ "2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", # junk after valid address
+ "2001:0000:1234: 0000:0000:C1C0:ABCD:0876", # internal space
+ "3ffe:0b00:0000:0001:0000:0000:000a", # seven segments
+ "FF02:0000:0000:0000:0000:0000:0000:0000:0001", # nine segments
+ "3ffe:b00::1::a", # double "::"
+ "::1111:2222:3333:4444:5555:6666::", # double "::"
+ "1:2:3::4:5::7:8", # Double "::"
+ "12345::6:7:8",
+ # IPv4 embedded, but bad...
+ "1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4",
+ "1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4",
+ "1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4",
+ "1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30",
+ "1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4",
+ "1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4",
+ "1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4",
+ "1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30",
+ "::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4",
+ "::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4",
+ "::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4",
+ "::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30",
+ "2001:1:1:1:1:1:255Z255X255Y255", # garbage instead of "." in IPv4
+ "::ffff:192x168.1.26", # ditto
+ "::ffff:2.3.4",
+ "::ffff:257.1.2.3",
+ "1.2.3.4:1111:2222:3333:4444::5555",
+ "1.2.3.4:1111:2222:3333::5555",
+ "1.2.3.4:1111:2222::5555",
+ "1.2.3.4:1111::5555",
+ "1.2.3.4::5555",
+ "1.2.3.4::",
+
+ # Testing IPv4 addresses represented as dotted-quads Leading zero's in
+ # IPv4 addresses not allowed: some systems treat the leading "0" in
+ # ".086" as the start of an octal number Update: The BNF in RFC-3986
+ # explicitly defines the dec-octet (for IPv4 addresses) not to have a
+ # leading zero
+ "fe80:0000:0000:0000:0204:61ff:254.157.241.086",
+ "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4",
+ "1111:2222:3333:4444:5555:6666:00.00.00.00",
+ "1111:2222:3333:4444:5555:6666:000.000.000.000",
+ "1111:2222:3333:4444:5555:6666:256.256.256.256",
+
+ "1111:2222:3333:4444::5555:",
+ "1111:2222:3333::5555:",
+ "1111:2222::5555:",
+ "1111::5555:",
+ "::5555:",
+ ":::",
+ "1111:",
+ ":",
+
+ ":1111:2222:3333:4444::5555",
+ ":1111:2222:3333::5555",
+ ":1111:2222::5555",
+ ":1111::5555",
+ ":::5555",
+ ":::",
+
+ # Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693
+ "123",
+ "ldkfj",
+ "2001::FFD3::57ab",
+ "2001:db8:85a3::8a2e:37023:7334",
+ "2001:db8:85a3::8a2e:370k:7334",
+ "1:2:3:4:5:6:7:8:9",
+ "1::2::3",
+ "1:::3:4:5",
+ "1:2:3::4:5:6:7:8:9",
+
+ # Invalid data
+ "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX",
+
+ # Too many components
+ "1111:2222:3333:4444:5555:6666:7777:8888:9999",
+ "1111:2222:3333:4444:5555:6666:7777:8888::",
+ "::2222:3333:4444:5555:6666:7777:8888:9999",
+
+ # Too few components
+ "1111:2222:3333:4444:5555:6666:7777",
+ "1111:2222:3333:4444:5555:6666",
+ "1111:2222:3333:4444:5555",
+ "1111:2222:3333:4444",
+ "1111:2222:3333",
+ "1111:2222",
+ "1111",
+
+ # Missing :
+ "11112222:3333:4444:5555:6666:7777:8888",
+ "1111:22223333:4444:5555:6666:7777:8888",
+ "1111:2222:33334444:5555:6666:7777:8888",
+ "1111:2222:3333:44445555:6666:7777:8888",
+ "1111:2222:3333:4444:55556666:7777:8888",
+ "1111:2222:3333:4444:5555:66667777:8888",
+ "1111:2222:3333:4444:5555:6666:77778888",
+
+ # Missing : intended for ::
+ "1111:2222:3333:4444:5555:6666:7777:8888:",
+ "1111:2222:3333:4444:5555:6666:7777:",
+ "1111:2222:3333:4444:5555:6666:",
+ "1111:2222:3333:4444:5555:",
+ "1111:2222:3333:4444:",
+ "1111:2222:3333:",
+ "1111:2222:",
+ "1111:",
+ ":",
+ ":8888",
+ ":7777:8888",
+ ":6666:7777:8888",
+ ":5555:6666:7777:8888",
+ ":4444:5555:6666:7777:8888",
+ ":3333:4444:5555:6666:7777:8888",
+ ":2222:3333:4444:5555:6666:7777:8888",
+ ":1111:2222:3333:4444:5555:6666:7777:8888",
+
+ # :::
+ ":::2222:3333:4444:5555:6666:7777:8888",
+ "1111:::3333:4444:5555:6666:7777:8888",
+ "1111:2222:::4444:5555:6666:7777:8888",
+ "1111:2222:3333:::5555:6666:7777:8888",
+ "1111:2222:3333:4444:::6666:7777:8888",
+ "1111:2222:3333:4444:5555:::7777:8888",
+ "1111:2222:3333:4444:5555:6666:::8888",
+ "1111:2222:3333:4444:5555:6666:7777:::",
+
+ # Double ::",
+ "::2222::4444:5555:6666:7777:8888",
+ "::2222:3333::5555:6666:7777:8888",
+ "::2222:3333:4444::6666:7777:8888",
+ "::2222:3333:4444:5555::7777:8888",
+ "::2222:3333:4444:5555:7777::8888",
+ "::2222:3333:4444:5555:7777:8888::",
+
+ "1111::3333::5555:6666:7777:8888",
+ "1111::3333:4444::6666:7777:8888",
+ "1111::3333:4444:5555::7777:8888",
+ "1111::3333:4444:5555:6666::8888",
+ "1111::3333:4444:5555:6666:7777::",
+
+ "1111:2222::4444::6666:7777:8888",
+ "1111:2222::4444:5555::7777:8888",
+ "1111:2222::4444:5555:6666::8888",
+ "1111:2222::4444:5555:6666:7777::",
+
+ "1111:2222:3333::5555::7777:8888",
+ "1111:2222:3333::5555:6666::8888",
+ "1111:2222:3333::5555:6666:7777::",
+
+ "1111:2222:3333:4444::6666::8888",
+ "1111:2222:3333:4444::6666:7777::",
+
+ "1111:2222:3333:4444:5555::7777::",
+
+
+ # Too many components"
+ "1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4",
+ "1111:2222:3333:4444:5555:6666:7777:1.2.3.4",
+ "1111:2222:3333:4444:5555:6666::1.2.3.4",
+ "::2222:3333:4444:5555:6666:7777:1.2.3.4",
+ "1111:2222:3333:4444:5555:6666:1.2.3.4.5",
+
+ # Too few components
+ "1111:2222:3333:4444:5555:1.2.3.4",
+ "1111:2222:3333:4444:1.2.3.4",
+ "1111:2222:3333:1.2.3.4",
+ "1111:2222:1.2.3.4",
+ "1111:1.2.3.4",
+
+ # Missing :
+ "11112222:3333:4444:5555:6666:1.2.3.4",
+ "1111:22223333:4444:5555:6666:1.2.3.4",
+ "1111:2222:33334444:5555:6666:1.2.3.4",
+ "1111:2222:3333:44445555:6666:1.2.3.4",
+ "1111:2222:3333:4444:55556666:1.2.3.4",
+ "1111:2222:3333:4444:5555:66661.2.3.4",
+
+ # Missing .
+ "1111:2222:3333:4444:5555:6666:255255.255.255",
+ "1111:2222:3333:4444:5555:6666:255.255255.255",
+ "1111:2222:3333:4444:5555:6666:255.255.255255",
+
+ # Missing : intended for ::
+ ":1.2.3.4",
+ ":6666:1.2.3.4",
+ ":5555:6666:1.2.3.4",
+ ":4444:5555:6666:1.2.3.4",
+ ":3333:4444:5555:6666:1.2.3.4",
+ ":2222:3333:4444:5555:6666:1.2.3.4",
+ ":1111:2222:3333:4444:5555:6666:1.2.3.4",
+
+ # :::
+ ":::2222:3333:4444:5555:6666:1.2.3.4",
+ "1111:::3333:4444:5555:6666:1.2.3.4",
+ "1111:2222:::4444:5555:6666:1.2.3.4",
+ "1111:2222:3333:::5555:6666:1.2.3.4",
+ "1111:2222:3333:4444:::6666:1.2.3.4",
+ "1111:2222:3333:4444:5555:::1.2.3.4",
+
+ # Double ::
+ "::2222::4444:5555:6666:1.2.3.4",
+ "::2222:3333::5555:6666:1.2.3.4",
+ "::2222:3333:4444::6666:1.2.3.4",
+ "::2222:3333:4444:5555::1.2.3.4",
+
+ "1111::3333::5555:6666:1.2.3.4",
+ "1111::3333:4444::6666:1.2.3.4",
+ "1111::3333:4444:5555::1.2.3.4",
+
+ "1111:2222::4444::6666:1.2.3.4",
+ "1111:2222::4444:5555::1.2.3.4",
+
+ "1111:2222:3333::5555::1.2.3.4",
+
+ # Missing parts
+ "::.",
+ "::..",
+ "::...",
+ "::1...",
+ "::1.2..",
+ "::1.2.3.",
+ "::.2..",
+ "::.2.3.",
+ "::.2.3.4",
+ "::..3.",
+ "::..3.4",
+ "::...4",
+
+ # Extra : in front
+ ":1111:2222:3333:4444:5555:6666:7777::",
+ ":1111:2222:3333:4444:5555:6666::",
+ ":1111:2222:3333:4444:5555::",
+ ":1111:2222:3333:4444::",
+ ":1111:2222:3333::",
+ ":1111:2222::",
+ ":1111::",
+ ":::",
+ ":1111:2222:3333:4444:5555:6666::8888",
+ ":1111:2222:3333:4444:5555::8888",
+ ":1111:2222:3333:4444::8888",
+ ":1111:2222:3333::8888",
+ ":1111:2222::8888",
+ ":1111::8888",
+ ":::8888",
+ ":1111:2222:3333:4444:5555::7777:8888",
+ ":1111:2222:3333:4444::7777:8888",
+ ":1111:2222:3333::7777:8888",
+ ":1111:2222::7777:8888",
+ ":1111::7777:8888",
+ ":::7777:8888",
+ ":1111:2222:3333:4444::6666:7777:8888",
+ ":1111:2222:3333::6666:7777:8888",
+ ":1111:2222::6666:7777:8888",
+ ":1111::6666:7777:8888",
+ ":::6666:7777:8888",
+ ":1111:2222:3333::5555:6666:7777:8888",
+ ":1111:2222::5555:6666:7777:8888",
+ ":1111::5555:6666:7777:8888",
+ ":::5555:6666:7777:8888",
+ ":1111:2222::4444:5555:6666:7777:8888",
+ ":1111::4444:5555:6666:7777:8888",
+ ":::4444:5555:6666:7777:8888",
+ ":1111::3333:4444:5555:6666:7777:8888",
+ ":::3333:4444:5555:6666:7777:8888",
+ ":::2222:3333:4444:5555:6666:7777:8888",
+ ":1111:2222:3333:4444:5555:6666:1.2.3.4",
+ ":1111:2222:3333:4444:5555::1.2.3.4",
+ ":1111:2222:3333:4444::1.2.3.4",
+ ":1111:2222:3333::1.2.3.4",
+ ":1111:2222::1.2.3.4",
+ ":1111::1.2.3.4",
+ ":::1.2.3.4",
+ ":1111:2222:3333:4444::6666:1.2.3.4",
+ ":1111:2222:3333::6666:1.2.3.4",
+ ":1111:2222::6666:1.2.3.4",
+ ":1111::6666:1.2.3.4",
+ ":::6666:1.2.3.4",
+ ":1111:2222:3333::5555:6666:1.2.3.4",
+ ":1111:2222::5555:6666:1.2.3.4",
+ ":1111::5555:6666:1.2.3.4",
+ ":::5555:6666:1.2.3.4",
+ ":1111:2222::4444:5555:6666:1.2.3.4",
+ ":1111::4444:5555:6666:1.2.3.4",
+ ":::4444:5555:6666:1.2.3.4",
+ ":1111::3333:4444:5555:6666:1.2.3.4",
+ ":::2222:3333:4444:5555:6666:1.2.3.4",
+
+ # Extra : at end
+ "1111:2222:3333:4444:5555:6666:7777:::",
+ "1111:2222:3333:4444:5555:6666:::",
+ "1111:2222:3333:4444:5555:::",
+ "1111:2222:3333:4444:::",
+ "1111:2222:3333:::",
+ "1111:2222:::",
+ "1111:::",
+ ":::",
+ "1111:2222:3333:4444:5555:6666::8888:",
+ "1111:2222:3333:4444:5555::8888:",
+ "1111:2222:3333:4444::8888:",
+ "1111:2222:3333::8888:",
+ "1111:2222::8888:",
+ "1111::8888:",
+ "::8888:",
+ "1111:2222:3333:4444:5555::7777:8888:",
+ "1111:2222:3333:4444::7777:8888:",
+ "1111:2222:3333::7777:8888:",
+ "1111:2222::7777:8888:",
+ "1111::7777:8888:",
+ "::7777:8888:",
+ "1111:2222:3333:4444::6666:7777:8888:",
+ "1111:2222:3333::6666:7777:8888:",
+ "1111:2222::6666:7777:8888:",
+ "1111::6666:7777:8888:",
+ "::6666:7777:8888:",
+ "1111:2222:3333::5555:6666:7777:8888:",
+ "1111:2222::5555:6666:7777:8888:",
+ "1111::5555:6666:7777:8888:",
+ "::5555:6666:7777:8888:",
+ "1111:2222::4444:5555:6666:7777:8888:",
+ "1111::4444:5555:6666:7777:8888:",
+ "::4444:5555:6666:7777:8888:",
+ "1111::3333:4444:5555:6666:7777:8888:",
+ "::3333:4444:5555:6666:7777:8888:",
+ "::2222:3333:4444:5555:6666:7777:8888:",
+ ].each do |ip|
+ it "should reject #{ip.inspect} as an IPv6 address" do
+ expect { @class.new(:name => "foo", :ip => ip) }.to raise_error(Puppet::ResourceError, /Parameter ip failed/)
+ end
+ end
+
+ it "should not accept newlines in resourcename" do
+ expect { @class.new(:name => "fo\no", :ip => '127.0.0.1' ) }.to raise_error(Puppet::ResourceError, /Hostname cannot include newline/)
+ end
+
+ it "should not accept newlines in ipaddress" do
+ expect { @class.new(:name => "foo", :ip => "127.0.0.1\n") }.to raise_error(Puppet::ResourceError, /Invalid IP address/)
+ end
+
+ it "should not accept newlines in host_aliases" do
+ expect { @class.new(:name => "foo", :ip => '127.0.0.1', :host_aliases => [ 'well_formed', "thisalias\nhavenewline" ] ) }.to raise_error(Puppet::ResourceError, /Host aliases cannot include whitespace/)
+ end
+
+ it "should not accept newlines in comment" do
+ expect { @class.new(:name => "foo", :ip => '127.0.0.1', :comment => "Test of comment blah blah \n test 123" ) }.to raise_error(Puppet::ResourceError, /Comment cannot include newline/)
+ end
+
+ it "should not accept spaces in resourcename" do
+ expect { @class.new(:name => "foo bar") }.to raise_error(Puppet::ResourceError, /Invalid host name/)
+ end
+
+ it "should not accept host_aliases with spaces" do
+ expect { @class.new(:name => "foo", :host_aliases => [ 'well_formed', 'not wellformed' ]) }.to raise_error(Puppet::ResourceError, /Host aliases cannot include whitespace/)
+ end
+
+ it "should not accept empty host_aliases" do
+ expect { @class.new(:name => "foo", :host_aliases => ['alias1','']) }.to raise_error(Puppet::ResourceError, /Host aliases cannot be an empty string/)
+ end
+ end
+
+ describe "when syncing" do
+
+ it "should send the first value to the provider for ip property" do
+ @ip = @class.attrclass(:ip).new(:resource => @resource, :should => %w{192.168.0.1 192.168.0.2})
+
+ @ip.sync
+
+ expect(@provider.ip).to eq('192.168.0.1')
+ end
+
+ it "should send the first value to the provider for comment property" do
+ @comment = @class.attrclass(:comment).new(:resource => @resource, :should => %w{Bazinga Notme})
+
+ @comment.sync
+
+ expect(@provider.comment).to eq('Bazinga')
+ end
+
+ it "should send the joined array to the provider for host_alias" do
+ @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
+
+ @host_aliases.sync
+
+ expect(@provider.host_aliases).to eq('foo bar')
+ end
+
+ it "should also use the specified delimiter for joining" do
+ @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
+ @host_aliases.stubs(:delimiter).returns "\t"
+
+ @host_aliases.sync
+
+ expect(@provider.host_aliases).to eq("foo\tbar")
+ end
+
+ it "should care about the order of host_aliases" do
+ @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
+ expect(@host_aliases.insync?(%w{foo bar})).to eq(true)
+ expect(@host_aliases.insync?(%w{bar foo})).to eq(false)
+ end
+
+ it "should not consider aliases to be in sync if should is a subset of current" do
+ @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar})
+ expect(@host_aliases.insync?(%w{foo bar anotherone})).to eq(false)
+ end
+
+ end
+end