Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions lib/puppet/parser/functions/validate_augeas.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Puppet::Parser::Functions
newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args|
Perform validation of a string using an Augeas lens
The first argument of this function should be a string to
test, and the second argument should be the name of the Augeas lens to use.
If Augeas fails to parse the string with the lens, the compilation will
abort with a parse error.

A third argument can be specified, listing paths which should
not be found in the file. The `$file` variable points to the location
of the temporary file being tested in the Augeas tree.

For example, if you want to make sure your passwd content never contains
a user `foo`, you could write:

validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo'])

Or if you wanted to ensure that no users used the '/bin/barsh' shell,
you could use:

validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]']

If a fourth argument is specified, this will be the error message raised and
seen by the user.

A helpful error message can be returned like this:

validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas')

ENDHEREDOC
unless Puppet.features.augeas?
raise Puppet::ParseError, ("validate_augeas(): requires the ruby augeas bindings")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, but I think it would be great if the error message helped the end user with next steps to make this work. For example, perhaps a URL to a document that describes how to enable the augeas feature in Puppet.

Hopefully we have this documented at http://docs.puppetlabs.com/

Maybe something like, "requires the ruby augeas bindings. Information about how to satisfy this requirements is available at: http://..."

I think this would be great because a ParseError aborts catalog compilation entirely, so the user is sort of "stuck" if they encounter this error.

end

if (args.length < 2) or (args.length > 4) then
raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)")
end

msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}"

require 'augeas'
aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD)
begin
content = args[0]

# Test content in a temporary file
tmpfile = Tempfile.new("validate_augeas")
begin
tmpfile.write(content)
ensure
tmpfile.close
end

# Check for syntax
lens = args[1]
aug.transform(
:lens => lens,
:name => 'Validate_augeas',
:incl => tmpfile.path
)
aug.load!

unless aug.match("/augeas/files#{tmpfile.path}//error").empty?
error = aug.get("/augeas/files#{tmpfile.path}//error/message")
msg += " with error: #{error}"
raise Puppet::ParseError, (msg)
end

# Launch unit tests
tests = args[2] || []
aug.defvar('file', "/files#{tmpfile.path}")
tests.each do |t|
msg += " testing path #{t}"
raise Puppet::ParseError, (msg) unless aug.match(t).empty?
end
ensure
aug.close
tmpfile.unlink
end
end
end
102 changes: 102 additions & 0 deletions spec/unit/puppet/parser/functions/validate_augeas_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require 'spec_helper'

describe Puppet::Parser::Functions.function(:validate_augeas), :if => Puppet.features.augeas? do
let(:scope) { PuppetlabsSpec::PuppetInternals.scope }

# The subject of these examplres is the method itself.
subject do
# This makes sure the function is loaded within each test
function_name = Puppet::Parser::Functions.function(:validate_augeas)
scope.method(function_name)
end

context 'Using Puppet::Parser::Scope.new' do

describe 'Garbage inputs' do
inputs = [
[ nil ],
[ [ nil ] ],
[ { 'foo' => 'bar' } ],
[ { } ],
[ '' ],
[ "one", "one", "MSG to User", "4th arg" ],
]

inputs.each do |input|
it "validate_augeas(#{input.inspect}) should fail" do
expect { subject.call [input] }.to raise_error Puppet::ParseError
end
end
end

describe 'Valid inputs' do
inputs = [
[ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns' ],
[ "proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns'],
]

inputs.each do |input|
it "validate_augeas(#{input.inspect}) should not fail" do
expect { subject.call input }.not_to raise_error
end
end
end

describe "Valid inputs which should raise an exception without a message" do
# The intent here is to make sure valid inputs raise exceptions when they
# don't specify an error message to display. This is the behvior in
# 2.2.x and prior.
inputs = [
[ "root:x:0:0:root\n", 'Passwd.lns' ],
[ "127.0.1.1\n", 'Hosts.lns' ],
]

inputs.each do |input|
it "validate_augeas(#{input.inspect}) should fail" do
expect { subject.call input }.to raise_error /validate_augeas.*?matched less than it should/
end
end
end

describe "Nicer Error Messages" do
# The intent here is to make sure the function returns the 3rd argument
# in the exception thrown
inputs = [
[ "root:x:0:0:root\n", 'Passwd.lns', [], 'Failed to validate passwd content' ],
[ "127.0.1.1\n", 'Hosts.lns', [], 'Wrong hosts content' ],
]

inputs.each do |input|
it "validate_augeas(#{input.inspect}) should fail" do
expect { subject.call input }.to raise_error /#{input[2]}/
end
end
end

describe "Passing simple unit tests" do
inputs = [
[ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']],
[ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']],
]

inputs.each do |input|
it "validate_augeas(#{input.inspect}) should fail" do
expect { subject.call input }.not_to raise_error
end
end
end

describe "Failing simple unit tests" do
inputs = [
[ "foobar:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']],
[ "root:x:0:0:root:/root:/bin/sh\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']],
]

inputs.each do |input|
it "validate_augeas(#{input.inspect}) should fail" do
expect { subject.call input }.to raise_error /testing path/
end
end
end
end
end