aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Cooper <josh@puppet.com>2018-07-13 09:38:02 -0700
committerGitHub <noreply@github.com>2018-07-13 09:38:02 -0700
commitbd20d920a19e9b011dfd027a802985cea46ae04f (patch)
treef882cd2b7c31ae08cd242882b0f7f1d1ed89cbbd
parent9d96ddecea6367abee1aa77859848b0c158fca80 (diff)
parentc957642d70f4b778736a9b49cf1ef9702e42c4f1 (diff)
downloadpuppet-hosts_core-bd20d920a19e9b011dfd027a802985cea46ae04f.tar.gz
puppet-hosts_core-bd20d920a19e9b011dfd027a802985cea46ae04f.tar.bz2
Merge pull request #1 from puppetlabs/extraction
Initial host module extraction
-rw-r--r--.fixtures.yml6
-rw-r--r--.gitattributes4
-rw-r--r--.gitignore24
-rw-r--r--.pdkignore24
-rw-r--r--.rspec2
-rw-r--r--.rubocop.yml119
-rw-r--r--.sync.yml34
-rw-r--r--.travis.yml44
-rw-r--r--.yardopts1
-rw-r--r--CHANGELOG.md11
-rw-r--r--Gemfile89
-rw-r--r--README.md54
-rw-r--r--REFERENCE.md57
-rw-r--r--Rakefile6
-rw-r--r--appveyor.yml52
-rw-r--r--lib/puppet/provider/host/parsed.rb49
-rw-r--r--lib/puppet/type/host.rb92
-rw-r--r--metadata.json90
-rw-r--r--spec/acceptance/nodesets/default.yml13
-rw-r--r--spec/acceptance/tests/create_spec.rb56
-rw-r--r--spec/acceptance/tests/destroy_spec.rb36
-rw-r--r--spec/acceptance/tests/modify_spec.rb35
-rw-r--r--spec/acceptance/tests/query_all_spec.rb37
-rw-r--r--spec/default_facts.yml8
-rw-r--r--spec/fixtures/unit/provider/host/parsed/valid_hosts19
-rw-r--r--spec/lib/puppet_spec/files.rb44
-rw-r--r--spec/shared_behaviours/all_parsedfile_providers.rb21
-rw-r--r--spec/spec_helper.rb45
-rw-r--r--spec/spec_helper_acceptance.rb13
-rw-r--r--spec/spec_helper_local.rb19
-rw-r--r--spec/unit/provider/host/parsed_spec.rb210
-rw-r--r--spec/unit/type/host_spec.rb665
32 files changed, 1977 insertions, 2 deletions
diff --git a/.fixtures.yml b/.fixtures.yml
new file mode 100644
index 0000000..2296adb
--- /dev/null
+++ b/.fixtures.yml
@@ -0,0 +1,6 @@
+# This file can be used to install module dependencies for unit testing
+# See https://github.com/puppetlabs/puppetlabs_spec_helper#using-fixtures for details
+---
+fixtures:
+ forge_modules:
+# stdlib: "puppetlabs/stdlib"
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..543dd6a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+*.rb eol=lf
+*.erb eol=lf
+*.pp eol=lf
+*.sh eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..650022e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+.git/
+.*.sw[op]
+.metadata
+.yardoc
+.yardwarns
+*.iml
+/.bundle/
+/.idea/
+/.vagrant/
+/coverage/
+/bin/
+/doc/
+/Gemfile.local
+/Gemfile.lock
+/junit/
+/log/
+/pkg/
+/spec/fixtures/manifests/
+/spec/fixtures/modules/
+/tmp/
+/vendor/
+/convert_report.txt
+/update_report.txt
+.DS_Store
diff --git a/.pdkignore b/.pdkignore
new file mode 100644
index 0000000..650022e
--- /dev/null
+++ b/.pdkignore
@@ -0,0 +1,24 @@
+.git/
+.*.sw[op]
+.metadata
+.yardoc
+.yardwarns
+*.iml
+/.bundle/
+/.idea/
+/.vagrant/
+/coverage/
+/bin/
+/doc/
+/Gemfile.local
+/Gemfile.lock
+/junit/
+/log/
+/pkg/
+/spec/fixtures/manifests/
+/spec/fixtures/modules/
+/tmp/
+/vendor/
+/convert_report.txt
+/update_report.txt
+.DS_Store
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..16f9cdb
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,2 @@
+--color
+--format documentation
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..403057e
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,119 @@
+---
+require: rubocop-rspec
+AllCops:
+ DisplayCopNames: true
+ TargetRubyVersion: '2.1'
+ Include:
+ - "./**/*.rb"
+ Exclude:
+ - bin/*
+ - ".vendor/**/*"
+ - "**/Gemfile"
+ - "**/Rakefile"
+ - pkg/**/*
+ - spec/fixtures/**/*
+ - vendor/**/*
+ - "**/Puppetfile"
+ - "**/Vagrantfile"
+ - "**/Guardfile"
+Metrics/LineLength:
+ Description: People have wide screens, use them.
+ Max: 200
+ Enabled: false
+RSpec/BeforeAfterAll:
+ Description: Beware of using after(:all) as it may cause state to leak between tests.
+ A necessary evil in acceptance testing.
+ Exclude:
+ - spec/acceptance/**/*.rb
+RSpec/HookArgument:
+ Description: Prefer explicit :each argument, matching existing module's style
+ EnforcedStyle: each
+Style/BlockDelimiters:
+ Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to
+ be consistent then.
+ EnforcedStyle: braces_for_chaining
+Style/ClassAndModuleChildren:
+ Description: Compact style reduces the required amount of indentation.
+ EnforcedStyle: compact
+Style/EmptyElse:
+ Description: Enforce against empty else clauses, but allow `nil` for clarity.
+ EnforcedStyle: empty
+Style/FormatString:
+ Description: Following the main puppet project's style, prefer the % format format.
+ EnforcedStyle: percent
+Style/FormatStringToken:
+ Description: Following the main puppet project's style, prefer the simpler template
+ tokens over annotated ones.
+ EnforcedStyle: template
+Style/Lambda:
+ Description: Prefer the keyword for easier discoverability.
+ EnforcedStyle: literal
+Style/RegexpLiteral:
+ Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168
+ EnforcedStyle: percent_r
+Style/TernaryParentheses:
+ Description: Checks for use of parentheses around ternary conditions. Enforce parentheses
+ on complex expressions for better readability, but seriously consider breaking
+ it up.
+ EnforcedStyle: require_parentheses_when_complex
+Style/TrailingCommaInArguments:
+ Description: Prefer always trailing comma on multiline argument lists. This makes
+ diffs, and re-ordering nicer.
+ EnforcedStyleForMultiline: comma
+Style/TrailingCommaInLiteral:
+ Description: Prefer always trailing comma on multiline literals. This makes diffs,
+ and re-ordering nicer.
+ EnforcedStyleForMultiline: comma
+Style/SymbolArray:
+ Description: Using percent style obscures symbolic intent of array's contents.
+ EnforcedStyle: brackets
+Layout/IndentHeredoc:
+ Enabled: false
+RSpec/MessageSpies:
+ EnforcedStyle: receive
+Style/Documentation:
+ Exclude:
+ - lib/puppet/parser/functions/**/*
+ - spec/**/*
+Style/WordArray:
+ EnforcedStyle: brackets
+Style/CollectionMethods:
+ Enabled: true
+Style/MethodCalledOnDoEndBlock:
+ Enabled: true
+Style/StringMethods:
+ Enabled: true
+Layout/EndOfLine:
+ Enabled: false
+Metrics/AbcSize:
+ Enabled: false
+Metrics/BlockLength:
+ Enabled: false
+Metrics/ClassLength:
+ Enabled: false
+Metrics/CyclomaticComplexity:
+ Enabled: false
+Metrics/MethodLength:
+ Enabled: false
+Metrics/ModuleLength:
+ Enabled: false
+Metrics/ParameterLists:
+ Enabled: false
+Metrics/PerceivedComplexity:
+ Enabled: false
+RSpec/DescribeClass:
+ Enabled: false
+RSpec/ExampleLength:
+ Enabled: false
+RSpec/MessageExpectation:
+ Enabled: false
+RSpec/MultipleExpectations:
+ Enabled: false
+RSpec/NestedGroups:
+ Enabled: false
+Style/AsciiComments:
+ Enabled: false
+Style/IfUnlessModifier:
+ Enabled: false
+Style/SymbolProc:
+ Enabled: false
diff --git a/.sync.yml b/.sync.yml
new file mode 100644
index 0000000..8c5ff48
--- /dev/null
+++ b/.sync.yml
@@ -0,0 +1,34 @@
+.rubocop.yml:
+ default_configs:
+ Metrics/LineLength:
+ Enabled: false
+ Layout/IndentHeredoc:
+ Enabled: false
+Gemfile:
+ required:
+ ':system_tests':
+ - gem: 'puppet-module-posix-system-r#{minor_version}'
+ platforms: ruby
+ - gem: 'puppet-module-win-system-r#{minor_version}'
+ platforms:
+ - mswin
+ - mingw
+ - x64_mingw
+ - gem: beaker
+ version: '~> 3.34'
+ from_env: BEAKER_VERSION
+ - gem: beaker-abs
+ from_env: BEAKER_ABS_VERSION
+ version: '~> 0.5'
+ - gem: beaker-pe
+ - gem: beaker-hostgenerator
+ from_env: BEAKER_HOSTGENERATOR_VERSION
+ - gem: beaker-rspec
+ from_env: BEAKER_RSPEC_VERSION
+ - gem: beaker-puppet
+ from_env: BEAKER_PUPPET_VERSION
+ version: '~> 0.14'
+ ':development':
+ - gem: puppet-strings
+.gitlab-ci.yml:
+ delete: true
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..81f77dd
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,44 @@
+---
+sudo: false
+dist: trusty
+language: ruby
+cache: bundler
+before_install:
+ - bundle -v
+ - rm -f Gemfile.lock
+ - gem update --system
+ - gem --version
+ - bundle -v
+script:
+ - 'bundle exec rake $CHECK'
+bundler_args: --without system_tests
+rvm:
+ - 2.4.1
+env:
+ global:
+ - BEAKER_PUPPET_COLLECTION=puppet5 PUPPET_GEM_VERSION="~> 5.0"
+matrix:
+ fast_finish: true
+ include:
+ -
+ env: CHECK="syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop"
+ -
+ env: CHECK=parallel_spec
+ -
+ env: PUPPET_GEM_VERSION="~> 4.0" CHECK=parallel_spec
+ rvm: 2.1.9
+branches:
+ only:
+ - master
+ - /^v\d/
+notifications:
+ email: false
+deploy:
+ provider: puppetforge
+ user: puppet
+ password:
+ secure: ""
+ on:
+ tags: true
+ all_branches: true
+ condition: "$DEPLOY_TO_FORGE = yes"
diff --git a/.yardopts b/.yardopts
new file mode 100644
index 0000000..29c933b
--- /dev/null
+++ b/.yardopts
@@ -0,0 +1 @@
+--markup markdown
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4c954cd
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## Release 0.1.0
+
+**Features**
+
+**Bugfixes**
+
+**Known Issues**
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..1c6557c
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,89 @@
+source ENV['GEM_SOURCE'] || 'https://rubygems.org'
+
+def location_for(place_or_version, fake_version = nil)
+ if place_or_version =~ %r{\A(git[:@][^#]*)#(.*)}
+ [fake_version, { git: Regexp.last_match(1), branch: Regexp.last_match(2), require: false }].compact
+ elsif place_or_version =~ %r{\Afile:\/\/(.*)}
+ ['>= 0', { path: File.expand_path(Regexp.last_match(1)), require: false }]
+ else
+ [place_or_version, { require: false }]
+ end
+end
+
+def gem_type(place_or_version)
+ if place_or_version =~ %r{\Agit[:@]}
+ :git
+ elsif !place_or_version.nil? && place_or_version.start_with?('file:')
+ :file
+ else
+ :gem
+ end
+end
+
+ruby_version_segments = Gem::Version.new(RUBY_VERSION.dup).segments
+minor_version = ruby_version_segments[0..1].join('.')
+
+group :development do
+ gem "fast_gettext", '1.1.0', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0')
+ gem "fast_gettext", require: false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0')
+ gem "json_pure", '<= 2.0.1', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
+ gem "json", '= 1.8.1', require: false if Gem::Version.new(RUBY_VERSION.dup) == Gem::Version.new('2.1.9')
+ gem "json", '<= 2.0.4', require: false if Gem::Version.new(RUBY_VERSION.dup) == Gem::Version.new('2.4.4')
+ gem "puppet-module-posix-default-r#{minor_version}", require: false, platforms: [:ruby]
+ gem "puppet-module-posix-dev-r#{minor_version}", require: false, platforms: [:ruby]
+ gem "puppet-module-win-default-r#{minor_version}", require: false, platforms: [:mswin, :mingw, :x64_mingw]
+ gem "puppet-module-win-dev-r#{minor_version}", require: false, platforms: [:mswin, :mingw, :x64_mingw]
+ gem "puppet-strings", require: false
+end
+group :system_tests do
+ gem "puppet-module-posix-system-r#{minor_version}", require: false, platforms: [:ruby]
+ gem "puppet-module-win-system-r#{minor_version}", require: false, platforms: [:mswin, :mingw, :x64_mingw]
+ gem "beaker", *location_for(ENV['BEAKER_VERSION'] || '~> 3.34')
+ gem "beaker-abs", *location_for(ENV['BEAKER_ABS_VERSION'] || '~> 0.5')
+ gem "beaker-pe", require: false
+ gem "beaker-hostgenerator"
+ gem "beaker-rspec"
+ gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 0.14')
+end
+
+puppet_version = ENV['PUPPET_GEM_VERSION']
+puppet_type = gem_type(puppet_version)
+facter_version = ENV['FACTER_GEM_VERSION']
+hiera_version = ENV['HIERA_GEM_VERSION']
+
+gems = {}
+
+gems['puppet'] = location_for(puppet_version)
+
+# If facter or hiera versions have been specified via the environment
+# variables
+
+gems['facter'] = location_for(facter_version) if facter_version
+gems['hiera'] = location_for(hiera_version) if hiera_version
+
+if Gem.win_platform? && puppet_version =~ %r{^(file:///|git://)}
+ # If we're using a Puppet gem on Windows which handles its own win32-xxx gem
+ # dependencies (>= 3.5.0), set the maximum versions (see PUP-6445).
+ gems['win32-dir'] = ['<= 0.4.9', require: false]
+ gems['win32-eventlog'] = ['<= 0.6.5', require: false]
+ gems['win32-process'] = ['<= 0.7.5', require: false]
+ gems['win32-security'] = ['<= 0.2.5', require: false]
+ gems['win32-service'] = ['0.8.8', require: false]
+end
+
+gems.each do |gem_name, gem_params|
+ gem gem_name, *gem_params
+end
+
+# Evaluate Gemfile.local and ~/.gemfile if they exist
+extra_gemfiles = [
+ "#{__FILE__}.local",
+ File.join(Dir.home, '.gemfile'),
+]
+
+extra_gemfiles.each do |gemfile|
+ if File.file?(gemfile) && File.readable?(gemfile)
+ eval(File.read(gemfile), binding)
+ end
+end
+# vim: syntax=ruby
diff --git a/README.md b/README.md
index f3b459f..81d1e11 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,52 @@
-# puppetlabs-host_core
-Install and manage host entries
+
+# host_core
+
+#### Table of Contents
+
+1. [Description](#description)
+2. [Usage - Configuration options and additional functionality](#usage)
+3. [Development - Guide for contributing to the module](#development)
+
+## Description
+
+The host_core module is used to manage host entries in a hosts file. For most systems, the hosts file is located in `/etc/hosts`.
+
+### Beginning with host_core
+
+To configure a `localhost` host entry to resolve to an `ip` with a list of `host_aliases` use the following:
+
+```
+host { 'localhost':
+ ensure => 'present',
+ host_aliases => ['localhost.localdomain', 'localhost4', 'localhost4.localdomain4'],
+ ip => '127.0.0.1',
+ target => '/etc/hosts',
+}
+```
+
+## Usage
+
+For details on usage, please see [the host puppet docs](https://puppet.com/docs/puppet/latest/types/host.html).
+
+## Reference
+
+Please see REFERENCE.md for the reference documentation.
+
+This module is documented using Puppet Strings.
+
+For a quick primer on how Strings works, please see [this blog post](https://puppet.com/blog/using-puppet-strings-generate-great-documentation-puppet-modules) or the [README.md](https://github.com/puppetlabs/puppet-strings/blob/master/README.md) for Puppet Strings.
+
+To generate documentation locally, run
+```
+bundle install
+bundle exec puppet strings generate ./lib/**/*.rb
+```
+This command will create a browsable `_index.html` file in the `doc` directory. The references available here are all generated from YARD-style comments embedded in the code base. When any development happens on this module, the impacted documentation should also be updated.
+
+## Development
+
+Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can't access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
+
+For more information, see our [module contribution guide.](https://docs.puppetlabs.com/forge/contributing.html)
diff --git a/REFERENCE.md b/REFERENCE.md
new file mode 100644
index 0000000..613a4df
--- /dev/null
+++ b/REFERENCE.md
@@ -0,0 +1,57 @@
+# Reference
+<!-- DO NOT EDIT: This document was generated by Puppet Strings -->
+
+## Table of Contents
+
+**Resource types**
+
+* [`host`](#host): Installs and manages host entries. For most systems, these entries will just be in `/etc/hosts`, but some systems (notably OS X) will have d
+
+## Resource types
+
+### host
+
+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.
+
+#### Properties
+
+The following properties are available in the `host` type.
+
+##### `ensure`
+
+Valid values: present, absent
+
+The basic property that the resource should be in.
+
+Default value: present
+
+##### `ip`
+
+The host's IP address, IPv4 or IPv6.
+
+##### `host_aliases`
+
+Any aliases the host might have. Multiple values must be
+specified as an array.
+
+##### `comment`
+
+A comment that will be attached to the line with a # character.
+
+##### `target`
+
+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`.
+
+#### Parameters
+
+The following parameters are available in the `host` type.
+
+##### `name`
+
+namevar
+
+The host name.
+
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..d4e36da
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,6 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-syntax/tasks/puppet-syntax'
+require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any?
+
+PuppetLint.configuration.send('disable_relative')
+
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..4a5b227
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,52 @@
+---
+version: 1.1.x.{build}
+skip_commits:
+ message: /^\(?doc\)?.*/
+clone_depth: 10
+init:
+ - SET
+ - 'mkdir C:\ProgramData\PuppetLabs\code && exit 0'
+ - 'mkdir C:\ProgramData\PuppetLabs\facter && exit 0'
+ - 'mkdir C:\ProgramData\PuppetLabs\hiera && exit 0'
+ - 'mkdir C:\ProgramData\PuppetLabs\puppet\var && exit 0'
+environment:
+ matrix:
+ -
+ RUBY_VERSION: 24-x64
+ CHECK: syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop
+ -
+ PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VERSION: 21
+ CHECK: parallel_spec
+ -
+ PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VERSION: 21-x64
+ CHECK: parallel_spec
+ -
+ PUPPET_GEM_VERSION: ~> 5.0
+ RUBY_VERSION: 24
+ CHECK: parallel_spec
+ -
+ PUPPET_GEM_VERSION: ~> 5.0
+ RUBY_VERSION: 24-x64
+ CHECK: parallel_spec
+matrix:
+ fast_finish: true
+install:
+ - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH%
+ - bundle install --jobs 4 --retry 2 --without system_tests
+ - type Gemfile.lock
+build: off
+test_script:
+ - bundle exec puppet -V
+ - ruby -v
+ - gem -v
+ - bundle -v
+ - bundle exec rake %CHECK%
+notifications:
+ - provider: Email
+ to:
+ - nobody@nowhere.com
+ on_build_success: false
+ on_build_failure: false
+ on_build_status_changed: false
diff --git a/lib/puppet/provider/host/parsed.rb b/lib/puppet/provider/host/parsed.rb
new file mode 100644
index 0000000..1f82237
--- /dev/null
+++ b/lib/puppet/provider/host/parsed.rb
@@ -0,0 +1,49 @@
+require 'puppet/provider/parsedfile'
+
+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
+ @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."
+
+ confine exists: hosts
+
+ text_line :comment, match: %r{^#}
+ text_line :blank, match: %r{^\s*$}
+ hosts_pattern = '^([0-9a-f:]\S+)\s+([^#\s+]\S+)\s*(.*?)?(?:\s*#\s*(.*))?$'
+ record_line :parsed, fields: ['ip', 'name', 'host_aliases', 'comment'],
+ optional: ['host_aliases', 'comment'],
+ match: %r{#{hosts_pattern}},
+ post_parse: proc { |hash|
+ # An absent comment should match "comment => ''"
+ hash[:comment] = '' if hash[:comment].nil? || hash[:comment] == :absent
+ unless hash[:host_aliases].nil? || hash[:host_aliases] == :absent
+ hash[:host_aliases].gsub!(%r{\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] && hash[n] != :absent
+ end
+ str = "#{hash[:ip]}\t#{hash[:name]}"
+ if hash.include?(:host_aliases) && !hash[:host_aliases].nil? && hash[:host_aliases] != :absent
+ str += "\t#{hash[:host_aliases]}"
+ end
+ if hash.include?(:comment) && !hash[:comment].empty?
+ str += "\t# #{hash[:comment]}"
+ end
+ str
+ }
+
+ text_line :incomplete, match: %r{(?! (#{hosts_pattern}))}
+end
diff --git a/lib/puppet/type/host.rb b/lib/puppet/type/host.rb
new file mode 100644
index 0000000..03059b9
--- /dev/null
+++ b/lib/puppet/type/host.rb
@@ -0,0 +1,92 @@
+require 'puppet/property/ordered_list'
+
+Puppet::Type.newtype(:host) do
+ @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."
+
+ ensurable
+
+ newproperty(:ip) do
+ desc "The host's IP address, IPv4 or IPv6."
+
+ def valid_v4?(addr)
+ data = addr.match(%r{^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$})
+ data && data.captures.map(&:to_i).all? { |i| i >= 0 && i <= 255 }
+ end
+
+ def valid_v6?(addr)
+ # http://forums.dartware.com/viewtopic.php?t=452
+ # ...and, yes, it is this hard. Doing it programmatically is harder.
+ addr =~ %r{^\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*$}
+ end
+
+ def valid_newline?(addr)
+ addr !~ %r{\n} && addr !~ %r{\r}
+ 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 =~ %r{\s}
+ raise Puppet::Error, _('Host aliases cannot be an empty string. Use an empty array to delete all host_aliases ') if value =~ %r{^\s*$}
+ end
+ end
+
+ newproperty(:comment) do
+ desc 'A comment that will be attached to the line with a # character.'
+ validate do |value|
+ if value =~ %r{\n} || value =~ %r{\r}
+ raise Puppet::Error, _('Comment cannot include newline')
+ end
+ 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 do
+ if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile)
+ @resource.class.defaultprovider.default_target
+ else
+ nil
+ end
+ end
+ end
+
+ newparam(:name) do
+ desc 'The host name.'
+
+ isnamevar
+
+ validate do |value|
+ value.split('.').each do |hostpart|
+ if hostpart !~ %r{^([\w]+|[\w][\w\-]+[\w])$}
+ raise Puppet::Error, _('Invalid host name')
+ end
+ end
+ if value =~ %r{\n} || value =~ %r{\r}
+ raise Puppet::Error, _('Hostname cannot include newline')
+ end
+ end
+ end
+end
diff --git a/metadata.json b/metadata.json
new file mode 100644
index 0000000..6bee2f3
--- /dev/null
+++ b/metadata.json
@@ -0,0 +1,90 @@
+{
+ "name": "puppetlabs-host_core",
+ "version": "0.1.0",
+ "author": "Puppet Labs",
+ "summary": "",
+ "license": "Apache-2.0",
+ "source": "",
+ "dependencies": [
+
+ ],
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "CentOS",
+ "operatingsystemrelease": [
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "OracleLinux",
+ "operatingsystemrelease": [
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "RedHat",
+ "operatingsystemrelease": [
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "Scientific",
+ "operatingsystemrelease": [
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "Debian",
+ "operatingsystemrelease": [
+ "8"
+ ]
+ },
+ {
+ "operatingsystem": "Ubuntu",
+ "operatingsystemrelease": [
+ "16.04"
+ ]
+ },
+ {
+ "operatingsystem": "windows",
+ "operatingsystemrelease": [
+ "2008 R2",
+ "2012 R2",
+ "10"
+ ]
+ },
+ {
+ "operatingsystem": "Fedora",
+ "operatingsystemrelease": [
+ "25"
+ ]
+ },
+ {
+ "operatingsystem": "Darwin",
+ "operatingsystemrelease": [
+ "16"
+ ]
+ },
+ {
+ "operatingsystem": "SLES",
+ "operatingsystemrelease": [
+ "12"
+ ]
+ },
+ {
+ "operatingsystem": "Solaris",
+ "operatingsystemrelease": [
+ "11"
+ ]
+ }
+ ],
+ "requirements": [
+ {
+ "name": "puppet",
+ "version_requirement": ">= 4.7.0 < 6.0.0"
+ }
+ ],
+ "pdk-version": "1.6.0",
+ "template-url": "file:///opt/puppetlabs/pdk/share/cache/pdk-templates.git",
+ "template-ref": "1.6.0-0-gf5564c0"
+}
diff --git a/spec/acceptance/nodesets/default.yml b/spec/acceptance/nodesets/default.yml
new file mode 100644
index 0000000..6323005
--- /dev/null
+++ b/spec/acceptance/nodesets/default.yml
@@ -0,0 +1,13 @@
+---
+HOSTS:
+ centos7-64-1:
+ hypervisor: vmpooler
+ platform: el-7-x86_64
+ packaging_platform: el-7-x86_64
+ template: centos-7-x86_64
+ roles:
+ - agent
+ - database
+CONFIG:
+ type: agent
+ pooling_api: http://vmpooler.delivery.puppetlabs.net/
diff --git a/spec/acceptance/tests/create_spec.rb b/spec/acceptance/tests/create_spec.rb
new file mode 100644
index 0000000..40267fd
--- /dev/null
+++ b/spec/acceptance/tests/create_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper_acceptance'
+
+RSpec.context 'when creating host files' do
+ agents.each do |agent|
+ context "on #{agent}" do
+ let(:target) { agent.tmpfile('host-create') }
+
+ after(:each) do
+ on(agent, "test #{target} && rm -f #{target}")
+ end
+
+ it 'creates a host record' do
+ on(agent, puppet_resource('host', 'test', 'ensure=present',
+ 'ip=127.0.0.1', "target=#{target}"))
+ on(agent, "cat #{target}") do |result|
+ fail_test 'record was not present' if result.stdout !~ %r{^127\.0\.0\.1[[:space:]]+test}
+ end
+ end
+
+ it 'creates host aliases' do
+ on(agent, puppet_resource('host', 'test', 'ensure=present',
+ 'ip=127.0.0.7', "target=#{target}", 'host_aliases=alias'))
+
+ on(agent, "cat #{target}") do |result|
+ fail_test 'alias was missing' unless
+ result.stdout =~ %r{^127\.0\.0\.7[[:space:]]+test[[:space:]]alias}
+ end
+ end
+
+ it "doesn't create the entry if it already exists" do
+ on agent, "printf '127.0.0.2 test alias\n' > #{target}"
+
+ step 'tell puppet to ensure the host exists'
+ on(agent, puppet_resource('host', 'test', "target=#{target}",
+ 'ensure=present', 'ip=127.0.0.2', 'host_aliases=alias')) do |result|
+ fail_test 'darn, we created the host record' if
+ result.stdout.include? '/Host[test1]/ensure: created'
+ end
+ end
+
+ it 'requires an ipaddress' do
+ skip_test if agent['locale'] == 'ja'
+
+ on(agent, puppet_resource('host', 'test', "target=#{target}",
+ 'host_aliases=alias')) do |result|
+ fail_test "puppet didn't complain about the missing attribute" unless
+ result.stderr.include? 'ip is a required attribute for hosts'
+ end
+
+ on(agent, "cat #{target}") do |result|
+ fail_test 'the host was apparently added to the file' if result.stdout.include? 'test'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/acceptance/tests/destroy_spec.rb b/spec/acceptance/tests/destroy_spec.rb
new file mode 100644
index 0000000..b00e03a
--- /dev/null
+++ b/spec/acceptance/tests/destroy_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper_acceptance'
+
+RSpec.context 'when managing host files' do
+ agents.each do |agent|
+ context "on #{agent}" do
+ let(:target) { agent.tmpfile('host-destroy') }
+
+ after(:each) do
+ on(agent, "test #{target} && rm -f #{target}")
+ end
+
+ it 'deletes a host record' do
+ line = '127.0.0.7 test1'
+
+ on agent, "printf '#{line}\n' > #{target}"
+ on(agent, puppet_resource('host', 'test1', "target=#{target}",
+ 'ensure=absent', 'ip=127.0.0.7'))
+ on(agent, "cat #{target}") do |result|
+ fail_test 'the content was still present' if result.stdout.include? line
+ end
+ end
+
+ it 'does not purge valid host records if file contains malformed content' do
+ on(agent, "printf '127.0.0.2 existing alias\n' > #{target}")
+ on(agent, "printf '==\n' >> #{target}")
+
+ on(agent, puppet_resource('host', 'test', "target=#{target}",
+ 'ensure=present', 'ip=127.0.0.3', 'host_aliases=foo'))
+
+ on(agent, "cat #{target}") do |result|
+ fail_test 'existing host data was deleted' unless result.stdout.include? 'existing'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/acceptance/tests/modify_spec.rb b/spec/acceptance/tests/modify_spec.rb
new file mode 100644
index 0000000..6498ee0
--- /dev/null
+++ b/spec/acceptance/tests/modify_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper_acceptance'
+
+RSpec.context 'when modifying host files' do
+ agents.each do |agent|
+ context "on #{agent}" do
+ let(:target) { agent.tmpfile('host-modify') }
+
+ after(:each) do
+ on(agent, "test #{target} && rm -f #{target}")
+ end
+
+ it 'modifies a host address' do
+ on agent, "printf '127.0.0.9 test alias\n' > #{target}"
+ on(agent, puppet_resource('host', 'test', "target=#{target}",
+ 'ensure=present', 'ip=127.0.0.10', 'host_aliases=alias'))
+
+ on(agent, "cat #{target}") do |result|
+ fail_test 'the address was not updated' unless
+ result.stdout =~ %r{^127\.0\.0\.10[[:space:]]+test[[:space:]]+alias[[:space:]]*$}
+ end
+ end
+
+ it 'modifies a host alias' do
+ on agent, "printf '127.0.0.8 test alias\n' > #{target}"
+ on(agent, puppet_resource('host', 'test', "target=#{target}",
+ 'ensure=present', 'ip=127.0.0.8', 'host_aliases=banzai'))
+
+ on(agent, "cat #{target}") do |result|
+ fail_test 'the alias was not updated' unless
+ result.stdout =~ %r{^127\.0\.0\.8[[:space:]]+test[[:space:]]+banzai[[:space:]]*$}
+ end
+ end
+ end
+ end
+end
diff --git a/spec/acceptance/tests/query_all_spec.rb b/spec/acceptance/tests/query_all_spec.rb
new file mode 100644
index 0000000..1653ab0
--- /dev/null
+++ b/spec/acceptance/tests/query_all_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper_acceptance'
+
+RSpec.context 'when querying all hosts from a host file' do
+ agents.each do |agent|
+ context "on #{agent}" do
+ let(:backup) { agent.tmpfile('host-query') }
+ let(:content) do
+ <<END
+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
+END
+ end
+
+ before(:each) do
+ on(agent, "cp /etc/hosts #{backup}")
+ on agent, 'cat > /etc/hosts', stdin: content
+ end
+
+ after(:each) do
+ on agent, "cat #{backup} > /etc/hosts && rm -f #{backup}"
+ end
+
+ it 'returns 4 host records' do
+ on(agent, puppet_resource('host')) do |result|
+ found = result.stdout.scan(%r{host \{ '([^']+)'}).flatten.sort
+ fail_test "the list of returned hosts was wrong: #{found.join(', ')}" unless
+ found == ['test1', 'test2', 'test3', 'test4']
+
+ count = result.stdout.scan(%r{ensure\s+=>\s+'present'}).length
+ fail_test "found #{count} records, wanted 4" unless count == 4
+ end
+ end
+ end
+ end
+end
diff --git a/spec/default_facts.yml b/spec/default_facts.yml
new file mode 100644
index 0000000..3248be5
--- /dev/null
+++ b/spec/default_facts.yml
@@ -0,0 +1,8 @@
+# Use default_module_facts.yml for module specific facts.
+#
+# Facts specified here will override the values provided by rspec-puppet-facts.
+---
+concat_basedir: "/tmp"
+ipaddress: "172.16.254.254"
+is_pe: false
+macaddress: "AA:AA:AA:AA:AA:AA"
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..fa774ef
--- /dev/null
+++ b/spec/lib/puppet_spec/files.rb
@@ -0,0 +1,44 @@
+require 'fileutils'
+require 'tempfile'
+require 'tmpdir'
+
+# A support module for testing files.
+module PuppetSpec::Files
+ @global_tempfiles = []
+
+ def self.cleanup
+ until @global_tempfiles.empty?
+ path = @global_tempfiles.pop
+ Dir.unstub(:entries)
+ FileUtils.rm_rf path, secure: true
+ end
+ end
+
+ module_function
+
+ def tmpfile(name, dir = nil)
+ dir ||= Dir.tmpdir
+ path = Puppet::FileSystem.expand_path(make_tmpname(name, nil).encode(Encoding::UTF_8), dir)
+ PuppetSpec::Files.record_tmp(File.expand_path(path))
+
+ path
+ end
+
+ # Copied from ruby 2.4 source
+ def make_tmpname((prefix, suffix), n)
+ prefix = (String.try_convert(prefix) ||
+ raise(ArgumentError, "unexpected prefix: #{prefix.inspect}"))
+ suffix &&= (String.try_convert(suffix) ||
+ raise(ArgumentError, "unexpected suffix: #{suffix.inspect}"))
+ t = Time.now.strftime('%Y%m%d')
+ path = "#{prefix}#{t}-#{$PROCESS_ID}-#{rand(0x100000000).to_s(36)}".dup
+ path << "-#{n}" if n
+ path << suffix if suffix
+ path
+ end
+
+ def self.record_tmp(tmp)
+ # ...record it for cleanup,
+ @global_tempfiles << tmp
+ 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..d697a14
--- /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?
+ 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!(%r{^# HEADER.+\n}, '')
+
+ oldlines = File.readlines(file)
+ newlines = text.chomp.split "\n"
+ oldlines.zip(newlines).each do |old, new|
+ expect(new.gsub(%r{\s+}, '')).to eq(old.chomp.gsub(%r{\s+}, ''))
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..e69d11d
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,45 @@
+
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'rspec-puppet-facts'
+
+begin
+ require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb'))
+rescue LoadError => loaderror
+ warn "Could not require spec_helper_local: #{loaderror.message}"
+end
+
+include RspecPuppetFacts
+
+default_facts = {
+ puppetversion: Puppet.version,
+ facterversion: Facter.version,
+}
+
+default_facts_path = File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml'))
+default_module_facts_path = File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml'))
+
+if File.exist?(default_facts_path) && File.readable?(default_facts_path)
+ default_facts.merge!(YAML.safe_load(File.read(default_facts_path)))
+end
+
+if File.exist?(default_module_facts_path) && File.readable?(default_module_facts_path)
+ default_facts.merge!(YAML.safe_load(File.read(default_module_facts_path)))
+end
+
+RSpec.configure do |c|
+ c.default_facts = default_facts
+ c.before :each do
+ # set to strictest setting for testing
+ # by default Puppet runs at warning level
+ Puppet.settings[:strict] = :warning
+ end
+end
+
+def ensure_module_defined(module_name)
+ module_name.split('::').reduce(Object) do |last_module, next_module|
+ last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module)
+ last_module.const_get(next_module)
+ end
+end
+
+# 'spec_overrides' from sync.yml will appear below this line
diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb
new file mode 100644
index 0000000..848f7d9
--- /dev/null
+++ b/spec/spec_helper_acceptance.rb
@@ -0,0 +1,13 @@
+require 'beaker-rspec'
+require 'beaker/module_install_helper'
+require 'beaker/puppet_install_helper'
+
+RSpec.configure do |c|
+ c.before :suite do
+ unless ENV['BEAKER_provision'] == 'no'
+ run_puppet_install_helper
+ install_module_on(hosts_as('default'))
+ install_module_dependencies_on(hosts)
+ end
+ end
+end
diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb
new file mode 100644
index 0000000..86c87f0
--- /dev/null
+++ b/spec/spec_helper_local.rb
@@ -0,0 +1,19 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+# So everyone else doesn't have to include this base constant.
+module PuppetSpec
+ FIXTURE_DIR = File.join(File.expand_path(File.dirname(__FILE__)), 'fixtures') unless defined?(FIXTURE_DIR)
+end
+
+require 'puppet_spec/files'
+
+Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour|
+ require behaviour.relative_path_from(Pathname.new(dir))
+end
+
+RSpec.configure do |c|
+ c.after :each do
+ PuppetSpec::Files.cleanup
+ 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..e07c83d
--- /dev/null
+++ b/spec/unit/provider/host/parsed_spec.rb
@@ -0,0 +1,210 @@
+require 'spec_helper'
+require 'shared_behaviours/all_parsedfile_providers'
+
+require 'puppet_spec/files'
+
+describe Puppet::Type.type(:host).provider(:parsed) do
+ include PuppetSpec::Files
+
+ let(:provider) { described_class }
+ let(:hostfile) { tmpfile('hosts') }
+
+ after :each do
+ provider.initvars
+ end
+
+ def mkhost(args)
+ hostresource = Puppet::Type::Host.new(name: args[:name], target: 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 && 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 'works for only ip' do
+ expect(provider.parse_line('127.0.0.1')[:line]).to eq('127.0.0.1')
+ end
+
+ it 'works for only hostname' do
+ expect(provider.parse_line('www.example.com')[:line]).to eq('www.example.com')
+ end
+
+ it 'works for ip and space' do
+ expect(provider.parse_line('127.0.0.1 ')[:line]).to eq('127.0.0.1 ')
+ end
+
+ it 'works for hostname and space' do
+ expect(provider.parse_line('www.example.com ')[:line]).to eq('www.example.com ')
+ end
+
+ it 'works 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 'works 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 'works for hostname and comment' do
+ expect(provider.parse_line('xyz #www test123')[:line]).to eq('xyz #www test123')
+ end
+
+ it 'works 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 'parses 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 'parses an ipv6 from the first field' do
+ expect(provider.parse_line('::1 localhost')[:ip]).to eq('::1')
+ end
+
+ it 'parses the name from the second field' do
+ expect(provider.parse_line('::1 localhost')[:name]).to eq('localhost')
+ end
+
+ it 'sets an empty comment' do
+ expect(provider.parse_line('::1 localhost')[:comment]).to eq('')
+ end
+
+ it 'sets 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
+ let(:testline) { '127.0.0.1 localhost # A comment with a #-char' }
+
+ it 'parses the ip from the first field' do
+ expect(provider.parse_line(testline)[:ip]).to eq('127.0.0.1')
+ end
+
+ it 'parses the hostname from the second field' do
+ expect(provider.parse_line(testline)[:name]).to eq('localhost')
+ end
+
+ it "parses 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 'parses 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 'parses 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
+ # Just playing with a few different delimiters
+ let(:testline) { "127.0.0.1\t host alias1\talias2 alias3 # A comment with a #-char" }
+
+ it 'parses the ip from the first field' do
+ expect(provider.parse_line(testline)[:ip]).to eq('127.0.0.1')
+ end
+
+ it 'parses the hostname from the second field' do
+ expect(provider.parse_line(testline)[:name]).to eq('host')
+ end
+
+ it 'parses all host_aliases from the third field' do
+ expect(provider.parse_line(testline)[:host_aliases]).to eq('alias1 alias2 alias3')
+ end
+
+ it "parses 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_behaves_like 'all parsedfile providers',
+ described_class, my_fixtures('valid*')
+
+ it 'is 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 'is 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 'is 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 'is 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 'is 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 'is 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..426657a
--- /dev/null
+++ b/spec/unit/type/host_spec.rb
@@ -0,0 +1,665 @@
+require 'spec_helper'
+
+FakeHostProvider = Struct.new(:ip, :host_aliases, :comment)
+
+describe Puppet::Type.type(:host) do
+ let(:provider) { FakeHostProvider.new }
+ let(:resource) { stub('resource', resource: nil, provider: provider) }
+
+ it 'has :name be its namevar' do
+ expect(described_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(described_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(described_class.attrtype(property)).to eq(:property)
+ end
+ end
+
+ it 'has a list host_aliases' do
+ expect(described_class.attrclass(:host_aliases).ancestors).to be_include(Puppet::Property::OrderedList)
+ end
+ end
+
+ describe 'when validating values' do
+ it 'supports present as a value for ensure' do
+ expect { described_class.new(name: 'foo', ensure: :present) }.not_to raise_error
+ end
+
+ it 'supports absent as a value for ensure' do
+ expect { described_class.new(name: 'foo', ensure: :absent) }.not_to raise_error
+ end
+
+ it 'accepts IPv4 addresses' do
+ expect { described_class.new(name: 'foo', ip: '10.96.0.1') }.not_to raise_error
+ end
+
+ it 'accepts long IPv6 addresses' do
+ # Taken from wikipedia article about ipv6
+ expect { described_class.new(name: 'foo', ip: '2001:0db8:85a3:08d3:1319:8a2e:0370:7344') }.not_to raise_error
+ end
+
+ it 'accepts one host_alias' do
+ expect { described_class.new(name: 'foo', host_aliases: 'alias1') }.not_to raise_error
+ end
+
+ it 'accepts multiple host_aliases' do
+ expect { described_class.new(name: 'foo', host_aliases: ['alias1', 'alias2']) }.not_to raise_error
+ end
+
+ it 'accepts shortened IPv6 addresses' do
+ expect { described_class.new(name: 'foo', ip: '2001:db8:0:8d3:0:8a2e:70:7344') }.not_to raise_error
+ expect { described_class.new(name: 'foo', ip: '::ffff:192.0.2.128') }.not_to raise_error
+ expect { described_class.new(name: 'foo', ip: '::1') }.not_to raise_error
+ end
+
+ it 'does not accept malformed IPv4 addresses like 192.168.0.300' do
+ expect { described_class.new(name: 'foo', ip: '192.168.0.300') }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed})
+ end
+
+ it 'rejects over-long IPv4 addresses' do
+ expect { described_class.new(name: 'foo', ip: '10.10.10.10.10') }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed})
+ end
+
+ it 'does not accept malformed IP addresses like 2001:0dg8:85a3:08d3:1319:8a2e:0370:7344' do
+ expect { described_class.new(name: 'foo', ip: '2001:0dg8:85a3:08d3:1319:8a2e:0370:7344') }.to raise_error(Puppet::ResourceError, %r{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 { described_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 { described_class.new(name: 'foo', ip: ip) }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed})
+ end
+ end
+
+ it 'does not accept newlines in resourcename' do
+ expect { described_class.new(name: "fo\no", ip: '127.0.0.1') }.to raise_error(Puppet::ResourceError, %r{Hostname cannot include newline})
+ end
+
+ it 'does not accept newlines in ipaddress' do
+ expect { described_class.new(name: 'foo', ip: "127.0.0.1\n") }.to raise_error(Puppet::ResourceError, %r{Invalid IP address})
+ end
+
+ it 'does not accept newlines in host_aliases' do
+ expect { described_class.new(name: 'foo', ip: '127.0.0.1', host_aliases: ['well_formed', "thisalias\nhavenewline"]) }.to raise_error(Puppet::ResourceError, %r{Host aliases cannot include whitespace})
+ end
+
+ it 'does not accept newlines in comment' do
+ expect { described_class.new(name: 'foo', ip: '127.0.0.1', comment: "Test of comment blah blah \n test 123") }.to raise_error(Puppet::ResourceError, %r{Comment cannot include newline})
+ end
+
+ it 'does not accept spaces in resourcename' do
+ expect { described_class.new(name: 'foo bar') }.to raise_error(Puppet::ResourceError, %r{Invalid host name})
+ end
+
+ it 'does not accept host_aliases with spaces' do
+ expect { described_class.new(name: 'foo', host_aliases: ['well_formed', 'not wellformed']) }.to raise_error(Puppet::ResourceError, %r{Host aliases cannot include whitespace})
+ end
+
+ it 'does not accept empty host_aliases' do
+ expect { described_class.new(name: 'foo', host_aliases: ['alias1', '']) }.to raise_error(Puppet::ResourceError, %r{Host aliases cannot be an empty string})
+ end
+ end
+
+ describe 'when syncing' do
+ it 'sends the first value to the provider for ip property' do
+ ip = described_class.attrclass(:ip).new(resource: resource, should: ['192.168.0.1', '192.168.0.2'])
+ ip.sync
+
+ expect(provider.ip).to eq('192.168.0.1')
+ end
+
+ it 'sends the first value to the provider for comment property' do
+ comment = described_class.attrclass(:comment).new(resource: resource, should: ['Bazinga', 'Notme'])
+ comment.sync
+
+ expect(provider.comment).to eq('Bazinga')
+ end
+
+ it 'sends the joined array to the provider for host_alias' do
+ host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar'])
+ host_aliases.sync
+
+ expect(provider.host_aliases).to eq('foo bar')
+ end
+
+ it 'alsoes use the specified delimiter for joining' do
+ host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar'])
+ host_aliases.stubs(:delimiter).returns "\t"
+ host_aliases.sync
+
+ expect(provider.host_aliases).to eq("foo\tbar")
+ end
+
+ it 'cares about the order of host_aliases' do
+ host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar'])
+ expect(host_aliases.insync?(['foo', 'bar'])).to eq(true)
+ expect(host_aliases.insync?(['bar', 'foo'])).to eq(false)
+ end
+
+ it 'does not consider aliases to be in sync if should is a subset of current' do
+ host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar'])
+ expect(host_aliases.insync?(['foo', 'bar', 'anotherone'])).to eq(false)
+ end
+ end
+end