summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG22
-rw-r--r--Gemfile11
-rw-r--r--Modulefile2
-rw-r--r--README.markdown17
-rw-r--r--lib/facter/facter_dot_d.rb202
-rw-r--r--lib/puppet/parser/functions/dirname.rb15
-rw-r--r--lib/puppet/parser/functions/ensure_resource.rb22
-rw-r--r--lib/puppet/parser/functions/hash.rb2
-rw-r--r--spec/functions/ensure_resource_spec.rb60
-rw-r--r--spec/unit/facter/pe_required_facts_spec.rb69
-rwxr-xr-xspec/unit/puppet/parser/functions/dirname_spec.rb24
11 files changed, 418 insertions, 28 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 26c942e..e1a095f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,25 @@
+2013-05-06 - Jeff McCune <jeff@puppetlabs.com> - 4.1.0
+ * (#20582) Restore facter_dot_d to stdlib for PE users (3b887c8)
+ * (maint) Update Gemfile with GEM_FACTER_VERSION (f44d535)
+
+2013-05-06 - Alex Cline <acline@us.ibm.com> - 4.1.0
+ * Terser method of string to array conversion courtesy of ethooz. (d38bce0)
+
+2013-05-06 - Alex Cline <acline@us.ibm.com> 4.1.0
+ * Refactor ensure_resource expectations (b33cc24)
+
+2013-05-06 - Alex Cline <acline@us.ibm.com> 4.1.0
+ * Changed str-to-array conversion and removed abbreviation. (de253db)
+
+2013-05-03 - Alex Cline <acline@us.ibm.com> 4.1.0
+ * (#20548) Allow an array of resource titles to be passed into the ensure_resource function (e08734a)
+
+2013-05-02 - Raphaƫl Pinson <raphael.pinson@camptocamp.com> - 4.1.0
+ * Add a dirname function (2ba9e47)
+
+2013-04-29 - Mark Smith-Guerrero <msmithgu@gmail.com> - 4.1.0
+ * (maint) Fix a small typo in hash() description (928036a)
+
2013-04-12 - Jeff McCune <jeff@puppetlabs.com> - 4.0.2
* Update user information in gemspec to make the intent of the Gem clear.
diff --git a/Gemfile b/Gemfile
index 50df2ee..197cc6b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -24,7 +24,16 @@ group :development, :test do
gem 'rspec-puppet', :require => false
end
-if puppetversion = ENV['PUPPET_GEM_VERSION']
+facterversion = ENV['GEM_FACTER_VERSION']
+if facterversion
+ gem 'facter', *location_for(facterversion)
+else
+ gem 'facter', :require => false
+end
+
+ENV['GEM_PUPPET_VERSION'] ||= ENV['PUPPET_GEM_VERSION']
+puppetversion = ENV['GEM_PUPPET_VERSION']
+if puppetversion
gem 'puppet', *location_for(puppetversion)
else
gem 'puppet', :require => false
diff --git a/Modulefile b/Modulefile
index 74304e5..9d2e8c2 100644
--- a/Modulefile
+++ b/Modulefile
@@ -1,5 +1,5 @@
name 'puppetlabs-stdlib'
-version '4.0.2'
+version '4.1.0'
source 'git://github.com/puppetlabs/puppetlabs-stdlib.git'
author 'puppetlabs'
license 'Apache 2.0'
diff --git a/README.markdown b/README.markdown
index 0efaf16..7b45b17 100644
--- a/README.markdown
+++ b/README.markdown
@@ -195,6 +195,16 @@ Would return: ['a','c']
- *Type*: rvalue
+dirname
+-------
+Returns the `dirname` of a path.
+
+*Examples:*
+
+ dirname('/path/to/a/file.ext')
+
+Would return: '/path/to/a'
+
downcase
--------
Converts the case of a string or all strings in an array to lower case.
@@ -233,6 +243,11 @@ If the resource already exists but does not match the specified parameters,
this function will attempt to recreate the resource leading to a duplicate
resource definition error.
+An array of resources can also be passed in and each will be created with
+the type and parameters specified if it doesn't already exist.
+
+ ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})
+
- *Type*: statement
@@ -390,7 +405,7 @@ Example:
hash
----
-This function converts and array into a hash.
+This function converts an array into a hash.
*Examples:*
diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb
new file mode 100644
index 0000000..e414b20
--- /dev/null
+++ b/lib/facter/facter_dot_d.rb
@@ -0,0 +1,202 @@
+# A Facter plugin that loads facts from /etc/facter/facts.d
+# and /etc/puppetlabs/facter/facts.d.
+#
+# Facts can be in the form of JSON, YAML or Text files
+# and any executable that returns key=value pairs.
+#
+# In the case of scripts you can also create a file that
+# contains a cache TTL. For foo.sh store the ttl as just
+# a number in foo.sh.ttl
+#
+# The cache is stored in /tmp/facts_cache.yaml as a mode
+# 600 file and will have the end result of not calling your
+# fact scripts more often than is needed
+
+class Facter::Util::DotD
+ require 'yaml'
+
+ def initialize(dir="/etc/facts.d", cache_file="/tmp/facts_cache.yml")
+ @dir = dir
+ @cache_file = cache_file
+ @cache = nil
+ @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml}
+ end
+
+ def entries
+ Dir.entries(@dir).reject{|f| f =~ /^\.|\.ttl$/}.sort.map {|f| File.join(@dir, f) }
+ rescue
+ []
+ end
+
+ def fact_type(file)
+ extension = File.extname(file)
+
+ type = @types[extension] || :unknown
+
+ type = :script if type == :unknown && File.executable?(file)
+
+ return type
+ end
+
+ def txt_parser(file)
+ File.readlines(file).each do |line|
+ if line =~ /^(.+)=(.+)$/
+ var = $1; val = $2
+
+ Facter.add(var) do
+ setcode { val }
+ end
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}")
+ end
+
+ def json_parser(file)
+ begin
+ require 'json'
+ rescue LoadError
+ retry if require 'rubygems'
+ raise
+ end
+
+ JSON.load(File.read(file)).each_pair do |f, v|
+ Facter.add(f) do
+ setcode { v }
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}")
+ end
+
+ def yaml_parser(file)
+ require 'yaml'
+
+ YAML.load_file(file).each_pair do |f, v|
+ Facter.add(f) do
+ setcode { v }
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}")
+ end
+
+ def script_parser(file)
+ result = cache_lookup(file)
+ ttl = cache_time(file)
+
+ unless result
+ result = Facter::Util::Resolution.exec(file)
+
+ if ttl > 0
+ Facter.debug("Updating cache for #{file}")
+ cache_store(file, result)
+ cache_save!
+ end
+ else
+ Facter.debug("Using cached data for #{file}")
+ end
+
+ result.split("\n").each do |line|
+ if line =~ /^(.+)=(.+)$/
+ var = $1; val = $2
+
+ Facter.add(var) do
+ setcode { val }
+ end
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}")
+ Facter.debug(e.backtrace.join("\n\t"))
+ end
+
+ def cache_save!
+ cache = load_cache
+ File.open(@cache_file, "w", 0600) {|f| f.write(YAML.dump(cache)) }
+ rescue
+ end
+
+ def cache_store(file, data)
+ load_cache
+
+ @cache[file] = {:data => data, :stored => Time.now.to_i}
+ rescue
+ end
+
+ def cache_lookup(file)
+ cache = load_cache
+
+ return nil if cache.empty?
+
+ ttl = cache_time(file)
+
+ if cache[file]
+ now = Time.now.to_i
+
+ return cache[file][:data] if ttl == -1
+ return cache[file][:data] if (now - cache[file][:stored]) <= ttl
+ return nil
+ else
+ return nil
+ end
+ rescue
+ return nil
+ end
+
+ def cache_time(file)
+ meta = file + ".ttl"
+
+ return File.read(meta).chomp.to_i
+ rescue
+ return 0
+ end
+
+ def load_cache
+ unless @cache
+ if File.exist?(@cache_file)
+ @cache = YAML.load_file(@cache_file)
+ else
+ @cache = {}
+ end
+ end
+
+ return @cache
+ rescue
+ @cache = {}
+ return @cache
+ end
+
+ def create
+ entries.each do |fact|
+ type = fact_type(fact)
+ parser = "#{type}_parser"
+
+ if respond_to?("#{type}_parser")
+ Facter.debug("Parsing #{fact} using #{parser}")
+
+ send(parser, fact)
+ end
+ end
+ end
+end
+
+
+mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/)
+if mdata
+ (major, minor, patch) = mdata.captures.map { |v| v.to_i }
+ if major < 2
+ # Facter 1.7 introduced external facts support directly
+ unless major == 1 and minor > 6
+ Facter::Util::DotD.new("/etc/facter/facts.d").create
+ Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create
+
+ # Windows has a different configuration directory that defaults to a vendor
+ # specific sub directory of the %COMMON_APPDATA% directory.
+ if Dir.const_defined? 'COMMON_APPDATA' then
+ windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d')
+ Facter::Util::DotD.new(windows_facts_dot_d).create
+ end
+ end
+ end
+end
diff --git a/lib/puppet/parser/functions/dirname.rb b/lib/puppet/parser/functions/dirname.rb
new file mode 100644
index 0000000..ea8cc1e
--- /dev/null
+++ b/lib/puppet/parser/functions/dirname.rb
@@ -0,0 +1,15 @@
+module Puppet::Parser::Functions
+ newfunction(:dirname, :type => :rvalue, :doc => <<-EOS
+ Returns the dirname of a path.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "dirname(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ path = arguments[0]
+ return File.dirname(path)
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/ensure_resource.rb b/lib/puppet/parser/functions/ensure_resource.rb
index fba2035..a9a1733 100644
--- a/lib/puppet/parser/functions/ensure_resource.rb
+++ b/lib/puppet/parser/functions/ensure_resource.rb
@@ -19,17 +19,27 @@ If the resource already exists but does not match the specified parameters,
this function will attempt to recreate the resource leading to a duplicate
resource definition error.
+An array of resources can also be passed in and each will be created with
+the type and parameters specified if it doesn't already exist.
+
+ ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})
+
ENDOFDOC
) do |vals|
type, title, params = vals
raise(ArgumentError, 'Must specify a type') unless type
raise(ArgumentError, 'Must specify a title') unless title
params ||= {}
- Puppet::Parser::Functions.function(:defined_with_params)
- if function_defined_with_params(["#{type}[#{title}]", params])
- Puppet.debug("Resource #{type}[#{title}] not created b/c it already exists")
- else
- Puppet::Parser::Functions.function(:create_resources)
- function_create_resources([type.capitalize, { title => params }])
+
+ items = [title].flatten
+
+ items.each do |item|
+ Puppet::Parser::Functions.function(:defined_with_params)
+ if function_defined_with_params(["#{type}[#{item}]", params])
+ Puppet.debug("Resource #{type}[#{item}] not created because it already exists")
+ else
+ Puppet::Parser::Functions.function(:create_resources)
+ function_create_resources([type.capitalize, { item => params }])
+ end
end
end
diff --git a/lib/puppet/parser/functions/hash.rb b/lib/puppet/parser/functions/hash.rb
index 453ba1e..8cc4823 100644
--- a/lib/puppet/parser/functions/hash.rb
+++ b/lib/puppet/parser/functions/hash.rb
@@ -4,7 +4,7 @@
module Puppet::Parser::Functions
newfunction(:hash, :type => :rvalue, :doc => <<-EOS
-This function converts and array into a hash.
+This function converts an array into a hash.
*Examples:*
diff --git a/spec/functions/ensure_resource_spec.rb b/spec/functions/ensure_resource_spec.rb
index 611666e..2e8aefc 100644
--- a/spec/functions/ensure_resource_spec.rb
+++ b/spec/functions/ensure_resource_spec.rb
@@ -4,17 +4,16 @@ require 'spec_helper'
require 'rspec-puppet'
describe 'ensure_resource' do
describe 'when a type or title is not specified' do
- it do
- should run.with_params().and_raise_error(ArgumentError)
- should run.with_params(['type']).and_raise_error(ArgumentError)
- end
+ it { should run.with_params().and_raise_error(ArgumentError) }
+ it { should run.with_params(['type']).and_raise_error(ArgumentError) }
end
+
describe 'when compared against a resource with no attributes' do
let :pre_condition do
'user { "dan": }'
end
- it do
- should run.with_params('user', 'dan', {})
+ it "should contain the the ensured resources" do
+ subject.should run.with_params('user', 'dan', {})
compiler.catalog.resource('User[dan]').to_s.should == 'User[dan]'
end
end
@@ -23,18 +22,43 @@ describe 'ensure_resource' do
let :pre_condition do
'user { "dan": ensure => present, shell => "/bin/csh", managehome => false}'
end
- it do
- # these first three should not fail
- should run.with_params('User', 'dan', {})
- should run.with_params('User', 'dan', '')
- should run.with_params('User', 'dan', {'ensure' => 'present'})
- should run.with_params('User', 'dan',
- {'ensure' => 'present', 'managehome' => false}
- )
- # test that this fails
- should run.with_params('User', 'dan',
- {'ensure' => 'absent', 'managehome' => false}
- ).and_raise_error(Puppet::Error)
+ # these first three should not fail
+ it { should run.with_params('User', 'dan', {}) }
+ it { should run.with_params('User', 'dan', '') }
+ it { should run.with_params('User', 'dan', {'ensure' => 'present'}) }
+ it { should run.with_params('User', 'dan', {'ensure' => 'present', 'managehome' => false}) }
+ # test that this fails
+ it { should run.with_params('User', 'dan', {'ensure' => 'absent', 'managehome' => false}).and_raise_error(Puppet::Error) }
+ end
+
+ describe 'when an array of new resources are passed in' do
+ it "should contain the ensured resources" do
+ subject.should run.with_params('User', ['dan', 'alex'], {})
+ compiler.catalog.resource('User[dan]').to_s.should == 'User[dan]'
+ compiler.catalog.resource('User[alex]').to_s.should == 'User[alex]'
+ end
+ end
+
+ describe 'when an array of existing resources is compared against existing resources' do
+ let :pre_condition do
+ 'user { "dan": ensure => present; "alex": ensure => present }'
+ end
+ it "should return the existing resources" do
+ subject.should run.with_params('User', ['dan', 'alex'], {})
+ compiler.catalog.resource('User[dan]').to_s.should == 'User[dan]'
+ compiler.catalog.resource('User[alex]').to_s.should == 'User[alex]'
+ end
+ end
+
+ describe 'when compared against existing resources with attributes' do
+ let :pre_condition do
+ 'user { "dan": ensure => present; "alex": ensure => present }'
end
+ # These should not fail
+ it { should run.with_params('User', ['dan', 'alex'], {}) }
+ it { should run.with_params('User', ['dan', 'alex'], '') }
+ it { should run.with_params('User', ['dan', 'alex'], {'ensure' => 'present'}) }
+ # This should fail
+ it { should run.with_params('User', ['dan', 'alex'], {'ensure' => 'absent'}).and_raise_error(Puppet::Error) }
end
end
diff --git a/spec/unit/facter/pe_required_facts_spec.rb b/spec/unit/facter/pe_required_facts_spec.rb
new file mode 100644
index 0000000..f219b37
--- /dev/null
+++ b/spec/unit/facter/pe_required_facts_spec.rb
@@ -0,0 +1,69 @@
+# Puppet Enterprise requires the following facts to be set in order to operate.
+# These facts are set using the file ???? and the two facts are
+# `fact_stomp_port`, and `fact_stomp_server`.
+#
+
+require 'spec_helper'
+
+describe "External facts in /etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt" do
+ context "With Facter 1.6.17 which does not have external facts support" do
+ before :each do
+ Facter.stubs(:version).returns("1.6.17")
+ # Stub out the filesystem for stdlib
+ Dir.stubs(:entries).with("/etc/puppetlabs/facter/facts.d").
+ returns(['puppet_enterprise_installer.txt'])
+ Dir.stubs(:entries).with("/etc/facter/facts.d").returns([])
+ File.stubs(:readlines).with('/etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt').
+ returns([
+ "fact_stomp_port=61613\n",
+ "fact_stomp_server=puppetmaster.acme.com\n",
+ "fact_is_puppetagent=true\n",
+ "fact_is_puppetmaster=false\n",
+ "fact_is_puppetca=false\n",
+ "fact_is_puppetconsole=false\n",
+ ])
+ if Facter.collection.respond_to? :load
+ Facter.collection.load(:facter_dot_d)
+ else
+ Facter.collection.loader.load(:facter_dot_d)
+ end
+ end
+
+ it 'defines fact_stomp_port' do
+ Facter.fact(:fact_stomp_port).value.should == '61613'
+ end
+ it 'defines fact_stomp_server' do
+ Facter.fact(:fact_stomp_server).value.should == 'puppetmaster.acme.com'
+ end
+ it 'defines fact_is_puppetagent' do
+ Facter.fact(:fact_is_puppetagent).value.should == 'true'
+ end
+ it 'defines fact_is_puppetmaster' do
+ Facter.fact(:fact_is_puppetmaster).value.should == 'false'
+ end
+ it 'defines fact_is_puppetca' do
+ Facter.fact(:fact_is_puppetca).value.should == 'false'
+ end
+ it 'defines fact_is_puppetconsole' do
+ Facter.fact(:fact_is_puppetconsole).value.should == 'false'
+ end
+ end
+
+ [ '1.7.1', '2.0.1' ].each do |v|
+ context "With Facter #{v} which has external facts support" do
+ before :each do
+ Facter.stubs(:version).returns(v)
+ end
+
+ it 'does not call Facter::Util::DotD.new' do
+ Facter::Util::DotD.expects(:new).never
+
+ if Facter.collection.respond_to? :load
+ Facter.collection.load(:facter_dot_d)
+ else
+ Facter.collection.loader.load(:facter_dot_d)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/puppet/parser/functions/dirname_spec.rb b/spec/unit/puppet/parser/functions/dirname_spec.rb
new file mode 100755
index 0000000..fb3b4fe
--- /dev/null
+++ b/spec/unit/puppet/parser/functions/dirname_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the dirname function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ Puppet::Parser::Functions.function("dirname").should == "function_dirname"
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ lambda { scope.function_dirname([]) }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should return dirname for an absolute path" do
+ result = scope.function_dirname(['/path/to/a/file.ext'])
+ result.should(eq('/path/to/a'))
+ end
+
+ it "should return dirname for a relative path" do
+ result = scope.function_dirname(['path/to/a/file.ext'])
+ result.should(eq('path/to/a'))
+ end
+end