diff options
-rw-r--r-- | .gemfile | 5 | ||||
-rw-r--r-- | .travis.yml | 16 | ||||
-rw-r--r-- | README.markdown | 7 | ||||
-rw-r--r-- | Rakefile | 18 | ||||
-rw-r--r-- | lib/facter/facter_dot_d.rb | 193 | ||||
-rw-r--r-- | lib/puppet/provider/file_line/ruby.rb | 36 | ||||
-rw-r--r-- | lib/puppet/type/file_line.rb | 12 | ||||
-rwxr-xr-x | spec/lib/puppet_spec/files.rb | 53 | ||||
-rwxr-xr-x | spec/lib/puppet_spec/fixtures.rb | 28 | ||||
-rw-r--r-- | spec/lib/puppet_spec/matchers.rb | 87 | ||||
-rwxr-xr-x | spec/lib/puppet_spec/verbose.rb | 9 | ||||
-rw-r--r-- | spec/spec_helper.rb | 1 | ||||
-rw-r--r-- | spec/unit/puppet/provider/file_line/ruby_spec.rb | 64 | ||||
-rw-r--r-- | spec/unit/puppet/type/file_line_spec.rb | 24 |
14 files changed, 162 insertions, 391 deletions
diff --git a/.gemfile b/.gemfile new file mode 100644 index 0000000..9aad840 --- /dev/null +++ b/.gemfile @@ -0,0 +1,5 @@ +source :rubygems + +puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 2.7'] +gem 'puppet', puppetversion +gem 'puppetlabs_spec_helper', '>= 0.1.0' diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0ec5a08 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: ruby +rvm: + - 1.8.7 +before_script: +after_script: +script: "rake spec_full" +branches: + only: + - master +env: + - PUPPET_VERSION=2.7.13 + - PUPPET_VERSION=2.7.6 + - PUPPET_VERSION=2.6.9 +notifications: + email: false +gemfile: .gemfile diff --git a/README.markdown b/README.markdown index 9fa4c38..027c2d2 100644 --- a/README.markdown +++ b/README.markdown @@ -80,3 +80,10 @@ automatically. This is a direct copy of R.I. Pienaar's custom facter fact located at: [https://github.com/ripienaar/facter-facts/tree/master/facts-dot-d](https://github.com/ripienaar/facter-facts/tree/master/facts-dot-d) +stdlib releases beyond 2.x will not include external fact support using +facts-dot-d. Instead, these versions of stdlib require Facter 2.0 which +includes external fact support in the main Facter codebase. + +Please see [2157](http://projects.puppetlabs.com/issues/2157) for more +information. + @@ -1,16 +1,2 @@ -require 'rake' -require 'rspec/core/rake_task' - -task :default => [:test] - -desc 'Run RSpec' -RSpec::Core::RakeTask.new(:test) do |t| - t.pattern = 'spec/{unit}/**/*.rb' - t.rspec_opts = ['--color'] -end - -desc 'Generate code coverage' -RSpec::Core::RakeTask.new(:coverage) do |t| - t.rcov = true - t.rcov_opts = ['--exclude', 'spec'] -end +require 'rubygems' +require 'puppetlabs_spec_helper/rake_tasks' diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb deleted file mode 100644 index c43801c..0000000 --- a/lib/facter/facter_dot_d.rb +++ /dev/null @@ -1,193 +0,0 @@ -# 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 - Puppet.deprecation_warning("TTL for external facts is being removed. See http://links.puppetlabs.com/factercaching for more information.") - 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 - -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 diff --git a/lib/puppet/provider/file_line/ruby.rb b/lib/puppet/provider/file_line/ruby.rb index f5d3a32..e21eaa8 100644 --- a/lib/puppet/provider/file_line/ruby.rb +++ b/lib/puppet/provider/file_line/ruby.rb @@ -1,3 +1,4 @@ + Puppet::Type.type(:file_line).provide(:ruby) do def exists? @@ -7,8 +8,10 @@ Puppet::Type.type(:file_line).provide(:ruby) do end def create - File.open(resource[:path], 'a') do |fh| - fh.puts resource[:line] + if resource[:match] + handle_create_with_match() + else + handle_create_without_match() end end @@ -21,7 +24,36 @@ Puppet::Type.type(:file_line).provide(:ruby) do private def lines + # If this type is ever used with very large files, we should + # write this in a different way, using a temp + # file; for now assuming that this type is only used on + # small-ish config files that can fit into memory without + # too much trouble. @lines ||= File.readlines(resource[:path]) end + def handle_create_with_match() + regex = resource[:match] ? Regexp.new(resource[:match]) : nil + match_count = lines.select { |l| regex.match(l) }.count + if match_count > 1 + raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" + end + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(regex.match(l) ? resource[:line] : l) + end + + if (match_count == 0) + fh.puts(resource[:line]) + end + end + end + + def handle_create_without_match + File.open(resource[:path], 'a') do |fh| + fh.puts resource[:line] + end + end + + end diff --git a/lib/puppet/type/file_line.rb b/lib/puppet/type/file_line.rb index f6fe1d0..6b35902 100644 --- a/lib/puppet/type/file_line.rb +++ b/lib/puppet/type/file_line.rb @@ -32,6 +32,11 @@ Puppet::Type.newtype(:file_line) do desc 'An arbitrary name used as the identity of the resource.' end + newparam(:match) do + desc 'An optional regular expression to run against existing lines in the file;\n' + + 'if a match is found, we replace that line rather than adding a new line.' + end + newparam(:line) do desc 'The line to be appended to the file located by the path parameter.' end @@ -49,5 +54,12 @@ Puppet::Type.newtype(:file_line) do unless self[:line] and self[:path] raise(Puppet::Error, "Both line and path are required attributes") end + + if (self[:match]) + unless Regexp.new(self[:match]).match(self[:line]) + raise(Puppet::Error, "When providing a 'match' parameter, the value must be a regex that matches against the value of your 'line' parameter") + end + end + end end diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb deleted file mode 100755 index 30fb4fc..0000000 --- a/spec/lib/puppet_spec/files.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'fileutils' -require 'tempfile' - -# A support module for testing files. -module PuppetSpec::Files - # This code exists only to support tests that run as root, pretty much. - # Once they have finally been eliminated this can all go... --daniel 2011-04-08 - if Puppet.features.posix? then - def self.in_tmp(path) - path =~ /^\/tmp/ or path =~ /^\/var\/folders/ - end - elsif Puppet.features.microsoft_windows? - def self.in_tmp(path) - tempdir = File.expand_path(File.join(Dir::LOCAL_APPDATA, "Temp")) - path =~ /^#{tempdir}/ - end - else - fail "Help! Can't find in_tmp for this platform" - end - - def self.cleanup - $global_tempfiles ||= [] - while path = $global_tempfiles.pop do - fail "Not deleting tmpfile #{path} outside regular tmpdir" unless in_tmp(path) - - begin - FileUtils.rm_r path, :secure => true - rescue Errno::ENOENT - # nothing to do - end - end - end - - def tmpfile(name) - # Generate a temporary file, just for the name... - source = Tempfile.new(name) - path = source.path - source.close! - - # ...record it for cleanup, - $global_tempfiles ||= [] - $global_tempfiles << File.expand_path(path) - - # ...and bam. - path - end - - def tmpdir(name) - path = tmpfile(name) - FileUtils.mkdir_p(path) - path - end -end diff --git a/spec/lib/puppet_spec/fixtures.rb b/spec/lib/puppet_spec/fixtures.rb deleted file mode 100755 index 7f6bc2a..0000000 --- a/spec/lib/puppet_spec/fixtures.rb +++ /dev/null @@ -1,28 +0,0 @@ -module PuppetSpec::Fixtures - def fixtures(*rest) - File.join(PuppetSpec::FIXTURE_DIR, *rest) - end - def my_fixture_dir - callers = caller - while line = callers.shift do - next unless found = line.match(%r{/spec/(.*)_spec\.rb:}) - return fixtures(found[1]) - end - fail "sorry, I couldn't work out your path from the caller stack!" - end - def my_fixture(name) - file = File.join(my_fixture_dir, name) - unless File.readable? file then - fail Puppet::DevError, "fixture '#{name}' for #{my_fixture_dir} is not readable" - end - return file - end - def my_fixtures(glob = '*', flags = 0) - files = Dir.glob(File.join(my_fixture_dir, glob), flags) - unless files.length > 0 then - fail Puppet::DevError, "fixture '#{glob}' for #{my_fixture_dir} had no files!" - end - block_given? and files.each do |file| yield file end - files - end -end diff --git a/spec/lib/puppet_spec/matchers.rb b/spec/lib/puppet_spec/matchers.rb deleted file mode 100644 index 77f5803..0000000 --- a/spec/lib/puppet_spec/matchers.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'stringio' - -######################################################################## -# Backward compatibility for Jenkins outdated environment. -module RSpec - module Matchers - module BlockAliases - alias_method :to, :should unless method_defined? :to - alias_method :to_not, :should_not unless method_defined? :to_not - alias_method :not_to, :should_not unless method_defined? :not_to - end - end -end - - -######################################################################## -# Custom matchers... -RSpec::Matchers.define :have_matching_element do |expected| - match do |actual| - actual.any? { |item| item =~ expected } - end -end - - -RSpec::Matchers.define :exit_with do |expected| - actual = nil - match do |block| - begin - block.call - rescue SystemExit => e - actual = e.status - end - actual and actual == expected - end - failure_message_for_should do |block| - "expected exit with code #{expected} but " + - (actual.nil? ? " exit was not called" : "we exited with #{actual} instead") - end - failure_message_for_should_not do |block| - "expected that exit would not be called with #{expected}" - end - description do - "expect exit with #{expected}" - end -end - - -RSpec::Matchers.define :have_printed do |expected| - match do |block| - $stderr = $stdout = StringIO.new - - begin - block.call - ensure - $stdout.rewind - @actual = $stdout.read - - $stdout = STDOUT - $stderr = STDERR - end - - if @actual then - case expected - when String - @actual.include? expected - when Regexp - expected.match @actual - else - raise ArgumentError, "No idea how to match a #{@actual.class.name}" - end - end - end - - failure_message_for_should do |actual| - if actual.nil? then - "expected #{expected.inspect}, but nothing was printed" - else - "expected #{expected.inspect} to be printed; got:\n#{actual}" - end - end - - description do - "expect #{expected.inspect} to be printed" - end - - diffable -end diff --git a/spec/lib/puppet_spec/verbose.rb b/spec/lib/puppet_spec/verbose.rb deleted file mode 100755 index d9834f2..0000000 --- a/spec/lib/puppet_spec/verbose.rb +++ /dev/null @@ -1,9 +0,0 @@ -# Support code for running stuff with warnings disabled. -module Kernel - def with_verbose_disabled - verbose, $VERBOSE = $VERBOSE, nil - result = yield - $VERBOSE = verbose - return result - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8ae9ad3..e269b90 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,4 +11,3 @@ gem 'rspec', '>=2.0.0' require 'rspec/expectations' require 'puppetlabs_spec_helper/module_spec_helper' - diff --git a/spec/unit/puppet/provider/file_line/ruby_spec.rb b/spec/unit/puppet/provider/file_line/ruby_spec.rb index b62e3a8..7857d39 100644 --- a/spec/unit/puppet/provider/file_line/ruby_spec.rb +++ b/spec/unit/puppet/provider/file_line/ruby_spec.rb @@ -2,8 +2,11 @@ require 'puppet' require 'tempfile' provider_class = Puppet::Type.type(:file_line).provider(:ruby) describe provider_class do - context "add" do + context "when adding" do before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files tmp = Tempfile.new('tmp') @tmpfile = tmp.path tmp.close! @@ -30,8 +33,65 @@ describe provider_class do end end - context "remove" do + context "when matching" do before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + } + ) + @provider = provider_class.new(@resource) + end + + it 'should raise an error if more than one line matches, and should not have modified the file' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + @provider.exists?.should be_nil + expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/) + File.read(@tmpfile).should eql("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + + it 'should replace a line that matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2") + end + @provider.exists?.should be_nil + @provider.create + File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2") + end + it 'should add a new line if no lines match' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2") + end + @provider.exists?.should be_nil + @provider.create + File.read(@tmpfile).should eql("foo1\nfoo2\nfoo = bar\n") + end + it 'should do nothing if the exact line already exists' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = bar\nfoo2") + end + @provider.exists?.should be_true + @provider.create + File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2") + end + end + + context "when removing" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files tmp = Tempfile.new('tmp') @tmpfile = tmp.path tmp.close! diff --git a/spec/unit/puppet/type/file_line_spec.rb b/spec/unit/puppet/type/file_line_spec.rb index c86dbd2..e1c07ac 100644 --- a/spec/unit/puppet/type/file_line_spec.rb +++ b/spec/unit/puppet/type/file_line_spec.rb @@ -7,6 +7,30 @@ describe Puppet::Type.type(:file_line) do it 'should accept a line and path' do file_line[:line] = 'my_line' file_line[:line].should == 'my_line' + file_line[:path] = '/my/path' + file_line[:path].should == '/my/path' + end + it 'should accept a match regex' do + file_line[:match] = '^foo.*$' + file_line[:match].should == '^foo.*$' + end + it 'should not accept a match regex that does not match the specified line' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'foo', + :path => '/my/path', + :line => 'foo=bar', + :match => '^bar=blah$' + )}.to raise_error(Puppet::Error, /the value must be a regex that matches/) + end + it 'should accept a match regex that does match the specified line' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'foo', + :path => '/my/path', + :line => 'foo=bar', + :match => '^\s*foo=.*$' + )}.not_to raise_error end it 'should accept posix filenames' do file_line[:path] = '/tmp/path' |