require 'spec_helper'
require 'puppet/util/package'
describe Puppet::Type.type(:augeas).provider(:augeas) do
let(:resource) do
Puppet::Type.type(:augeas).new(
name: 'test',
root: my_fixture_dir,
provider: :augeas,
)
end
let(:provider) do
resource.provider
end
let(:logs) do
# rubocop:disable RSpec/InstanceVariable
@logs
# rubocop:enable RSpec/InstanceVariable
end
after(:each) do
provider.close_augeas
end
def my_fixture_dir
File.expand_path(File.join(File.dirname(__FILE__), '../../../fixtures/unit/provider/augeas/augeas'))
end
def tmpfile(name)
Puppet::FileSystem.expand_path(make_tmpname(name, nil).encode(Encoding::UTF_8), Dir.tmpdir)
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
describe 'command parsing' do
it 'ignores nil values when parsing commands' do
commands = [nil, 'set Jar/Jar Binks']
tokens = provider.parse_commands(commands)
expect(tokens.size).to eq(1)
expect(tokens[0].size).to eq(3)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('Jar/Jar')
expect(tokens[0][2]).to eq('Binks')
end
it 'breaks apart a single line into three tokens and clean up the context' do
resource[:context] = '/context'
tokens = provider.parse_commands('set Jar/Jar Binks')
expect(tokens.size).to eq(1)
expect(tokens[0].size).to eq(3)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/context/Jar/Jar')
expect(tokens[0][2]).to eq('Binks')
end
it 'breaks apart a multiple line into six tokens' do
tokens = provider.parse_commands("set /Jar/Jar Binks\nrm anakin")
expect(tokens.size).to eq(2)
expect(tokens[0].size).to eq(3)
expect(tokens[1].size).to eq(2)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/Jar/Jar')
expect(tokens[0][2]).to eq('Binks')
expect(tokens[1][0]).to eq('rm')
expect(tokens[1][1]).to eq('anakin')
end
it 'strips whitespace and ignore blank lines' do
tokens = provider.parse_commands(" set /Jar/Jar Binks \t\n \n\n rm anakin ")
expect(tokens.size).to eq(2)
expect(tokens[0].size).to eq(3)
expect(tokens[1].size).to eq(2)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/Jar/Jar')
expect(tokens[0][2]).to eq('Binks')
expect(tokens[1][0]).to eq('rm')
expect(tokens[1][1]).to eq('anakin')
end
it 'handles arrays' do
resource[:context] = '/foo/'
commands = ['set /Jar/Jar Binks', 'rm anakin']
tokens = provider.parse_commands(commands)
expect(tokens.size).to eq(2)
expect(tokens[0].size).to eq(3)
expect(tokens[1].size).to eq(2)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/Jar/Jar')
expect(tokens[0][2]).to eq('Binks')
expect(tokens[1][0]).to eq('rm')
expect(tokens[1][1]).to eq('/foo/anakin')
end
# This is not supported in the new parsing class
# it "should concat the last values" do
# provider = provider_class.new
# tokens = provider.parse_commands("set /Jar/Jar Binks is my copilot")
# tokens.size.should == 1
# tokens[0].size.should == 3
# tokens[0][0].should == "set"
# tokens[0][1].should == "/Jar/Jar"
# tokens[0][2].should == "Binks is my copilot"
# end
it 'accepts spaces in the value and single ticks' do
resource[:context] = '/foo/'
tokens = provider.parse_commands("set JarJar 'Binks is my copilot'")
expect(tokens.size).to eq(1)
expect(tokens[0].size).to eq(3)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/foo/JarJar')
expect(tokens[0][2]).to eq('Binks is my copilot')
end
it 'accepts spaces in the value and double ticks' do
resource[:context] = '/foo/'
tokens = provider.parse_commands('set /JarJar "Binks is my copilot"')
expect(tokens.size).to eq(1)
expect(tokens[0].size).to eq(3)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/JarJar')
expect(tokens[0][2]).to eq('Binks is my copilot')
end
it 'accepts mixed ticks' do
resource[:context] = '/foo/'
tokens = provider.parse_commands('set JarJar "Some \'Test\'"')
expect(tokens.size).to eq(1)
expect(tokens[0].size).to eq(3)
expect(tokens[0][0]).to eq('set')
expect(tokens[0][1]).to eq('/foo/JarJar')
expect(tokens[0][2]).to eq("Some \'Test\'")
end
it 'handles predicates with literals' do
resource[:context] = '/foo/'
tokens = provider.parse_commands("rm */*[module='pam_console.so']")
expect(tokens).to eq([['rm', "/foo/*/*[module='pam_console.so']"]])
end
it 'handles whitespace in predicates' do
resource[:context] = '/foo/'
tokens = provider.parse_commands("ins 42 before /files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]")
expect(tokens).to eq([['ins', '42', 'before', "/files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]"]])
end
it 'handles multiple predicates' do
resource[:context] = '/foo/'
tokens = provider.parse_commands("clear pam.d/*/*[module = 'system-auth'][type = 'account']")
expect(tokens).to eq([['clear', "/foo/pam.d/*/*[module = 'system-auth'][type = 'account']"]])
end
it 'handles nested predicates' do
resource[:context] = '/foo/'
args = ['clear', "/foo/pam.d/*/*[module[ ../type = 'type] = 'system-auth'][type[last()] = 'account']"]
tokens = provider.parse_commands(args.join(' '))
expect(tokens).to eq([args])
end
it 'handles escaped doublequotes in doublequoted string' do
resource[:context] = '/foo/'
tokens = provider.parse_commands("set /foo \"''\\\"''\"")
expect(tokens).to eq([['set', '/foo', "''\"''"]])
end
it 'preserves escaped single quotes in double quoted strings' do
resource[:context] = '/foo/'
tokens = provider.parse_commands("set /foo \"\\'\"")
expect(tokens).to eq([['set', '/foo', "\\'"]])
end
it 'allows escaped spaces and brackets in paths' do
resource[:context] = '/foo/'
args = ['set', '/white\\ space/\\[section', 'value']
tokens = provider.parse_commands(args.join(" \t "))
expect(tokens).to eq([args])
end
it 'allows single quoted escaped spaces in paths' do
resource[:context] = '/foo/'
args = ['set', "'/white\\ space/key'", 'value']
tokens = provider.parse_commands(args.join(" \t "))
expect(tokens).to eq([['set', '/white\\ space/key', 'value']])
end
it 'allows double quoted escaped spaces in paths' do
resource[:context] = '/foo/'
args = ['set', '"/white\\ space/key"', 'value']
tokens = provider.parse_commands(args.join(" \t "))
expect(tokens).to eq([['set', '/white\\ space/key', 'value']])
end
it 'removes trailing slashes' do
resource[:context] = '/foo/'
tokens = provider.parse_commands('set foo/ bar')
expect(tokens).to eq([['set', '/foo/foo', 'bar']])
end
end
describe 'get filters' do
let(:augeas) { stub('augeas', get: 'value') }
before(:each) do
augeas.stubs('close')
provider.aug = augeas
end
it 'returns false for a = nonmatch' do
command = ['get', 'fake value', '==', 'value']
expect(provider.process_get(command)).to eq(true)
end
it 'returns true for a != match' do
command = ['get', 'fake value', '!=', 'value']
expect(provider.process_get(command)).to eq(false)
end
it 'returns true for a =~ match' do
command = ['get', 'fake value', '=~', 'val*']
expect(provider.process_get(command)).to eq(true)
end
it 'returns false for a == nonmatch' do
command = ['get', 'fake value', '=~', 'num*']
expect(provider.process_get(command)).to eq(false)
end
end
describe 'values filters' do
let(:augeas) { stub('augeas', match: ['set', 'of', 'values']) }
before(:each) do
augeas.stubs(:get).returns('set').then.returns('of').then.returns('values')
augeas.stubs('close')
provider.aug = augeas
end
it 'returns true for includes match' do
command = ['values', 'fake value', 'include values']
expect(provider.process_values(command)).to eq(true)
end
it 'returns false for includes non match' do
command = ['values', 'fake value', 'include JarJar']
expect(provider.process_values(command)).to eq(false)
end
it 'returns true for not_include non match' do
command = ['values', 'fake value', 'not_include JarJar']
expect(provider.process_values(command)).to eq(true)
end
it 'returns false for non_include match' do
command = ['values', 'fake value', 'not_include values']
expect(provider.process_values(command)).to eq(false)
end
it 'returns true for an array match' do
command = ['values', 'fake value', "== ['set', 'of', 'values']"]
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with double quotes and spaces' do
command = ['values', 'fake value', '== [ "set" , "of" , "values" ] ']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with internally escaped single quotes' do
provider.aug.stubs(:match).returns(['set', "o'values", 'here'])
provider.aug.stubs(:get).returns('set').then.returns("o'values").then.returns('here')
command = ['values', 'fake value', "== [ 'set', 'o\\'values', 'here']"]
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with octal character sequences' do
command = ['values', 'fake value', '== ["\\x73et", "of", "values"]']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with hex character sequences' do
command = ['values', 'fake value', '== ["\\163et", "of", "values"]']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with short unicode escape sequences' do
command = ['values', 'fake value', '== ["\\u0073et", "of", "values"]']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with single character long unicode escape sequences' do
command = ['values', 'fake value', '== ["\\u{0073}et", "of", "values"]']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with multi-character long unicode escape sequences' do
command = ['values', 'fake value', '== ["\\u{0073 0065 0074}", "of", "values"]']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array match with literal backslashes' do
provider.aug.stubs(:match).returns(['set', 'o\\values', 'here'])
provider.aug.stubs(:get).returns('set').then.returns('o\\values').then.returns('here')
command = ['values', 'fake value', '== [ "set", "o\\\\values", "here"]']
expect(provider.process_values(command)).to eq(true)
end
it 'returns false for an array non match' do
command = ['values', 'fake value', "== ['this', 'should', 'not', 'match']"]
expect(provider.process_values(command)).to eq(false)
end
it 'returns false for an array match with noteq' do
command = ['values', 'fake value', "!= ['set', 'of', 'values']"]
expect(provider.process_values(command)).to eq(false)
end
it 'returns true for an array non match with noteq' do
command = ['values', 'fake value', "!= ['this', 'should', 'not', 'match']"]
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an array non match with double quotes and spaces' do
command = ['values', 'fake value', '!= [ "this" , "should" ,"not", "match" ] ']
expect(provider.process_values(command)).to eq(true)
end
it 'returns true for an empty array match' do
provider.aug.stubs(:match).returns([])
provider.aug.stubs(:get)
command = ['values', 'fake value', '== []']
expect(provider.process_values(command)).to eq(true)
end
end
describe 'match filters' do
let(:augeas) { stub('augeas', match: ['set', 'of', 'values']) }
before(:each) do
augeas.stubs('close')
provider.aug = augeas
end
it 'returns true for size match' do
command = ['match', 'fake value', 'size == 3']
expect(provider.process_match(command)).to eq(true)
end
it 'returns false for a size non match' do
command = ['match', 'fake value', 'size < 3']
expect(provider.process_match(command)).to eq(false)
end
it 'returns true for includes match' do
command = ['match', 'fake value', 'include values']
expect(provider.process_match(command)).to eq(true)
end
it 'returns false for includes non match' do
command = ['match', 'fake value', 'include JarJar']
expect(provider.process_match(command)).to eq(false)
end
it 'returns true for not_includes non match' do
command = ['match', 'fake value', 'not_include JarJar']
expect(provider.process_match(command)).to eq(true)
end
it 'returns false for not_includes match' do
command = ['match', 'fake value', 'not_include values']
expect(provider.process_match(command)).to eq(false)
end
it 'returns true for an array match' do
command = ['match', 'fake value', "== ['set', 'of', 'values']"]
expect(provider.process_match(command)).to eq(true)
end
it 'returns true for an array match with double quotes and spaces' do
command = ['match', 'fake value', '== [ "set" , "of" , "values" ] ']
expect(provider.process_match(command)).to eq(true)
end
it 'returns false for an array non match' do
command = ['match', 'fake value', "== ['this', 'should', 'not', 'match']"]
expect(provider.process_match(command)).to eq(false)
end
it 'returns false for an array match with noteq' do
command = ['match', 'fake value', "!= ['set', 'of', 'values']"]
expect(provider.process_match(command)).to eq(false)
end
it 'returns true for an array non match with noteq' do
command = ['match', 'fake value', "!= ['this', 'should', 'not', 'match']"]
expect(provider.process_match(command)).to eq(true)
end
it 'returns true for an array non match with double quotes and spaces' do
command = ['match', 'fake value', '!= [ "this" , "should" ,"not", "match" ] ']
expect(provider.process_match(command)).to eq(true)
end
end
describe 'need to run' do
let(:augeas) { stub('augeas') }
before(:each) do
augeas.stubs('close')
provider.aug = augeas
# These tests pretend to be an earlier version so the provider doesn't
# attempt to make the change in the need_to_run? method
provider.stubs(:get_augeas_version).returns('0.3.5')
end
it 'handles no filters' do
augeas.stubs('match').returns(['set', 'of', 'values'])
expect(provider.need_to_run?).to eq(true)
end
it 'returns true when a get filter matches' do
resource[:onlyif] = 'get path == value'
augeas.stubs('get').returns('value')
expect(provider.need_to_run?).to eq(true)
end
describe 'performing numeric comparisons (#22617)' do
it 'returns true when a get string compare is true' do
resource[:onlyif] = 'get bpath > a'
augeas.stubs('get').returns('b')
expect(provider.need_to_run?).to eq(true)
end
it 'returns false when a get string compare is false' do
resource[:onlyif] = 'get a19path > a2'
augeas.stubs('get').returns('a19')
expect(provider.need_to_run?).to eq(false)
end
it 'returns true when a get int gt compare is true' do
resource[:onlyif] = 'get path19 > 2'
augeas.stubs('get').returns('19')
expect(provider.need_to_run?).to eq(true)
end
it 'returns true when a get int ge compare is true' do
resource[:onlyif] = 'get path19 >= 2'
augeas.stubs('get').returns('19')
expect(provider.need_to_run?).to eq(true)
end
it 'returns true when a get int lt compare is true' do
resource[:onlyif] = 'get path2 < 19'
augeas.stubs('get').returns('2')
expect(provider.need_to_run?).to eq(true)
end
it 'returns false when a get int le compare is false' do
resource[:onlyif] = 'get path39 <= 4'
augeas.stubs('get').returns('39')
expect(provider.need_to_run?).to eq(false)
end
end
describe 'performing is_numeric checks (#22617)' do
it 'returns false for nil' do
expect(provider.is_numeric?(nil)).to eq(false)
end
it 'returns true for Integers' do
expect(provider.is_numeric?(9)).to eq(true)
end
it 'returns true for numbers in Strings' do
expect(provider.is_numeric?('9')).to eq(true)
end
it 'returns false for non-number Strings' do
expect(provider.is_numeric?('x9')).to eq(false)
end
it 'returns false for other types' do
expect(provider.is_numeric?([true])).to eq(false)
end
end
it 'returns false when a get filter does not match' do
resource[:onlyif] = 'get path == another value'
augeas.stubs('get').returns('value')
expect(provider.need_to_run?).to eq(false)
end
it 'returns true when a match filter matches' do
resource[:onlyif] = 'match path size == 3'
augeas.stubs('match').returns(['set', 'of', 'values'])
expect(provider.need_to_run?).to eq(true)
end
it 'returns false when a match filter does not match' do
resource[:onlyif] = 'match path size == 2'
augeas.stubs('match').returns(['set', 'of', 'values'])
expect(provider.need_to_run?).to eq(false)
end
# Now setting force to true
it 'setting force should not change the above logic' do
resource[:force] = true
resource[:onlyif] = 'match path size == 2'
augeas.stubs('match').returns(['set', 'of', 'values'])
expect(provider.need_to_run?).to eq(false)
end
# Ticket 5211 testing
it 'returns true when a size != the provided value' do
resource[:onlyif] = 'match path size != 17'
augeas.stubs('match').returns(['set', 'of', 'values'])
expect(provider.need_to_run?).to eq(true)
end
# Ticket 5211 testing
it 'returns false when a size does equal the provided value' do
resource[:onlyif] = 'match path size != 3'
augeas.stubs('match').returns(['set', 'of', 'values'])
expect(provider.need_to_run?).to eq(false)
end
[true, false].product([true, false]) do |cfg, param|
describe "and Puppet[:show_diff] is #{cfg} and show_diff => #{param}" do
let(:file) { '/some/random/file' }
before(:each) do
Puppet[:show_diff] = cfg
resource[:show_diff] = param
resource[:root] = ''
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
File.stubs(:delete)
provider.stubs(:get_augeas_version).returns('0.10.0')
provider.stubs('diff').with(file.to_s, "#{file}.augnew").returns('diff')
augeas.stubs(:set).returns(true)
augeas.stubs(:save).returns(true)
augeas.stubs(:match).with('/augeas/events/saved').returns(['/augeas/events/saved'])
augeas.stubs(:get).with('/augeas/events/saved').returns("/files#{file}")
augeas.stubs(:set).with('/augeas/save', 'newfile')
end
if cfg && param
it 'displays a diff' do
expect(provider).to be_need_to_run
expect(logs[0].message).to eq("\ndiff")
end
else
it 'does not display a diff' do
expect(provider).to be_need_to_run
expect(logs).to be_empty
end
end
end
end
# Ticket 2728 (diff files)
describe 'and configured to show diffs' do
before(:each) do
Puppet[:show_diff] = true
resource[:show_diff] = true
resource[:root] = ''
provider.stubs(:get_augeas_version).returns('0.10.0')
augeas.stubs(:set).returns(true)
augeas.stubs(:save).returns(true)
end
it 'displays a diff when a single file is shown to have been changed' do
file = '/etc/hosts'
File.stubs(:delete)
resource[:loglevel] = 'crit'
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
augeas.stubs(:match).with('/augeas/events/saved').returns(['/augeas/events/saved'])
augeas.stubs(:get).with('/augeas/events/saved').returns("/files#{file}")
augeas.expects(:set).with('/augeas/save', 'newfile')
provider.expects('diff').with(file.to_s, "#{file}.augnew").returns('diff')
expect(provider).to be_need_to_run
expect(logs[0].message).to eq("\ndiff")
expect(logs[0].level).to eq(:crit)
end
it 'displays a diff for each file that is changed when changing many files' do
file1 = '/etc/hosts'
file2 = '/etc/resolv.conf'
File.stubs(:delete)
resource[:context] = '/files'
resource[:changes] = ["set #{file1}/foo bar", "set #{file2}/baz biz"]
augeas.stubs(:match).with('/augeas/events/saved').returns(['/augeas/events/saved[1]', '/augeas/events/saved[2]'])
augeas.stubs(:get).with('/augeas/events/saved[1]').returns("/files#{file1}")
augeas.stubs(:get).with('/augeas/events/saved[2]').returns("/files#{file2}")
augeas.expects(:set).with('/augeas/save', 'newfile')
provider.expects(:diff).with(file1.to_s, "#{file1}.augnew").returns("diff #{file1}")
provider.expects(:diff).with(file2.to_s, "#{file2}.augnew").returns("diff #{file2}")
expect(provider).to be_need_to_run
expect(logs.map(&:message)).to include("\ndiff #{file1}", "\ndiff #{file2}")
expect(logs.map(&:level)).to eq([:notice, :notice])
end
describe 'and resource[:root] is set' do
it 'calls diff when a file is shown to have been changed' do
root = '/tmp/foo'
file = '/etc/hosts'
File.stubs(:delete)
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
resource[:root] = root
augeas.stubs(:match).with('/augeas/events/saved').returns(['/augeas/events/saved'])
augeas.stubs(:get).with('/augeas/events/saved').returns("/files#{file}")
augeas.expects(:set).with('/augeas/save', 'newfile')
provider.expects(:diff).with("#{root}#{file}", "#{root}#{file}.augnew").returns('diff')
expect(provider).to be_need_to_run
expect(logs[0].message).to eq("\ndiff")
expect(logs[0].level).to eq(:notice)
end
end
it 'does not call diff if no files change' do
file = '/etc/hosts'
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
augeas.stubs(:match).with('/augeas/events/saved').returns([])
augeas.expects(:set).with('/augeas/save', 'newfile')
augeas.expects(:get).with('/augeas/events/saved').never
augeas.expects(:close)
provider.expects(:diff).never
expect(provider).not_to be_need_to_run
end
it 'cleanups the .augnew file' do
file = '/etc/hosts'
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
augeas.stubs(:match).with('/augeas/events/saved').returns(['/augeas/events/saved'])
augeas.stubs(:get).with('/augeas/events/saved').returns("/files#{file}")
augeas.expects(:set).with('/augeas/save', 'newfile')
augeas.expects(:close)
File.expects(:delete).with(file + '.augnew')
provider.expects(:diff).with(file.to_s, "#{file}.augnew").returns('')
expect(provider).to be_need_to_run
end
# Workaround for Augeas bug #264 which reports filenames twice
it 'handles duplicate /augeas/events/saved filenames' do
file = '/etc/hosts'
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
augeas.stubs(:match).with('/augeas/events/saved').returns(['/augeas/events/saved[1]', '/augeas/events/saved[2]'])
augeas.stubs(:get).with('/augeas/events/saved[1]').returns("/files#{file}")
augeas.stubs(:get).with('/augeas/events/saved[2]').returns("/files#{file}")
augeas.expects(:set).with('/augeas/save', 'newfile')
augeas.expects(:close)
File.expects(:delete).with(file + '.augnew').once
provider.expects(:diff).with(file.to_s, "#{file}.augnew").returns('').once
expect(provider).to be_need_to_run
end
it 'fails with an error if saving fails' do
file = '/etc/hosts'
resource[:context] = '/files'
resource[:changes] = ["set #{file}/foo bar"]
augeas.stubs(:save).returns(false)
augeas.stubs(:match).with('/augeas/events/saved').returns([])
augeas.expects(:close)
provider.expects(:diff).never
provider.expects(:print_put_errors)
expect { provider.need_to_run? }.to raise_error(Puppet::Error)
end
end
end
describe 'augeas execution integration' do
let(:augeas) { stub('augeas', :load) }
before(:each) do
augeas.stubs('close')
augeas.stubs(:match).with('/augeas/events/saved').returns([])
provider.aug = augeas
provider.stubs(:get_augeas_version).returns('0.3.5')
end
it 'handles set commands' do
resource[:changes] = 'set JarJar Binks'
resource[:context] = '/some/path/'
augeas.expects(:set).with('/some/path/JarJar', 'Binks').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles rm commands' do
resource[:changes] = 'rm /Jar/Jar'
augeas.expects(:rm).with('/Jar/Jar')
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles remove commands' do
resource[:changes] = 'remove /Jar/Jar'
augeas.expects(:rm).with('/Jar/Jar')
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles clear commands' do
resource[:changes] = 'clear Jar/Jar'
resource[:context] = '/foo/'
augeas.expects(:clear).with('/foo/Jar/Jar').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
describe 'touch command' do
it 'clears missing path' do
resource[:changes] = 'touch Jar/Jar'
resource[:context] = '/foo/'
augeas.expects(:match).with('/foo/Jar/Jar').returns([])
augeas.expects(:clear).with('/foo/Jar/Jar').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'does not change on existing path' do
resource[:changes] = 'touch Jar/Jar'
resource[:context] = '/foo/'
augeas.expects(:match).with('/foo/Jar/Jar').returns(['/foo/Jar/Jar'])
augeas.expects(:clear).never
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
end
it 'handles ins commands with before' do
resource[:changes] = 'ins Binks before Jar/Jar'
resource[:context] = '/foo'
augeas.expects(:insert).with('/foo/Jar/Jar', 'Binks', true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles ins commands with after' do
resource[:changes] = 'ins Binks after /Jar/Jar'
resource[:context] = '/foo'
augeas.expects(:insert).with('/Jar/Jar', 'Binks', false)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles ins with no context' do
resource[:changes] = 'ins Binks after /Jar/Jar'
augeas.expects(:insert).with('/Jar/Jar', 'Binks', false)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles multiple commands' do
resource[:changes] = ['ins Binks after /Jar/Jar', 'clear Jar/Jar']
resource[:context] = '/foo/'
augeas.expects(:insert).with('/Jar/Jar', 'Binks', false)
augeas.expects(:clear).with('/foo/Jar/Jar').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles defvar commands' do
resource[:changes] = 'defvar myjar Jar/Jar'
resource[:context] = '/foo/'
augeas.expects(:defvar).with('myjar', '/foo/Jar/Jar').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'passes through augeas variables without context' do
resource[:changes] = ['defvar myjar Jar/Jar', 'set $myjar/Binks 1']
resource[:context] = '/foo/'
augeas.expects(:defvar).with('myjar', '/foo/Jar/Jar').returns(true)
# this is the important bit, shouldn't be /foo/$myjar/Binks
augeas.expects(:set).with('$myjar/Binks', '1').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles defnode commands' do
resource[:changes] = 'defnode newjar Jar/Jar[last()+1] Binks'
resource[:context] = '/foo/'
augeas.expects(:defnode).with('newjar', '/foo/Jar/Jar[last()+1]', 'Binks').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles mv commands' do
resource[:changes] = 'mv Jar/Jar Binks'
resource[:context] = '/foo/'
augeas.expects(:mv).with('/foo/Jar/Jar', '/foo/Binks').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles rename commands' do
resource[:changes] = 'rename Jar/Jar Binks'
resource[:context] = '/foo/'
augeas.expects(:rename).with('/foo/Jar/Jar', 'Binks').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'handles setm commands' do
resource[:changes] = ['set test[1]/Jar/Jar Foo', 'set test[2]/Jar/Jar Bar', 'setm test Jar/Jar Binks']
resource[:context] = '/foo/'
augeas.expects(:respond_to?).with('setm').returns(true)
augeas.expects(:set).with('/foo/test[1]/Jar/Jar', 'Foo').returns(true)
augeas.expects(:set).with('/foo/test[2]/Jar/Jar', 'Bar').returns(true)
augeas.expects(:setm).with('/foo/test', 'Jar/Jar', 'Binks').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'throws error if setm command not supported' do
resource[:changes] = ['set test[1]/Jar/Jar Foo', 'set test[2]/Jar/Jar Bar', 'setm test Jar/Jar Binks']
resource[:context] = '/foo/'
augeas.expects(:respond_to?).with('setm').returns(false)
augeas.expects(:set).with('/foo/test[1]/Jar/Jar', 'Foo').returns(true)
augeas.expects(:set).with('/foo/test[2]/Jar/Jar', 'Bar').returns(true)
expect { provider.execute_changes }.to raise_error RuntimeError, %r{command 'setm' not supported}
end
it 'handles clearm commands' do
resource[:changes] = ['set test[1]/Jar/Jar Foo', 'set test[2]/Jar/Jar Bar', 'clearm test Jar/Jar']
resource[:context] = '/foo/'
augeas.expects(:respond_to?).with('clearm').returns(true)
augeas.expects(:set).with('/foo/test[1]/Jar/Jar', 'Foo').returns(true)
augeas.expects(:set).with('/foo/test[2]/Jar/Jar', 'Bar').returns(true)
augeas.expects(:clearm).with('/foo/test', 'Jar/Jar').returns(true)
augeas.expects(:save).returns(true)
augeas.expects(:close)
expect(provider.execute_changes).to eq(:executed)
end
it 'throws error if clearm command not supported' do
resource[:changes] = ['set test[1]/Jar/Jar Foo', 'set test[2]/Jar/Jar Bar', 'clearm test Jar/Jar']
resource[:context] = '/foo/'
augeas.expects(:respond_to?).with('clearm').returns(false)
augeas.expects(:set).with('/foo/test[1]/Jar/Jar', 'Foo').returns(true)
augeas.expects(:set).with('/foo/test[2]/Jar/Jar', 'Bar').returns(true)
expect { provider.execute_changes }.to raise_error(RuntimeError, %r{command 'clearm' not supported})
end
it 'throws error if saving failed' do
resource[:changes] = ['set test[1]/Jar/Jar Foo', 'set test[2]/Jar/Jar Bar', 'clearm test Jar/Jar']
resource[:context] = '/foo/'
augeas.expects(:respond_to?).with('clearm').returns(true)
augeas.expects(:set).with('/foo/test[1]/Jar/Jar', 'Foo').returns(true)
augeas.expects(:set).with('/foo/test[2]/Jar/Jar', 'Bar').returns(true)
augeas.expects(:clearm).with('/foo/test', 'Jar/Jar').returns(true)
augeas.expects(:save).returns(false)
provider.expects(:print_put_errors)
augeas.expects(:match).returns([])
expect { provider.execute_changes }.to raise_error(Puppet::Error)
end
end
describe 'when making changes', if: Puppet.features.augeas? do
it "does not clobber the file if it's a symlink" do
Puppet::Util::Storage.stubs(:store)
link = tmpfile('link')
target = tmpfile('target')
FileUtils.touch(target)
Puppet::FileSystem.symlink(target, link)
resource = Puppet::Type.type(:augeas).new(
name: 'test',
incl: link,
lens: 'Sshd.lns',
changes: 'set PermitRootLogin no',
)
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
catalog.apply
expect(File.ftype(link)).to eq('link')
expect(Puppet::FileSystem.readlink(link)).to eq(target)
expect(File.read(target)).to match(%r{PermitRootLogin no})
end
end
describe 'load/save failure reporting' do
let(:augeas) { stub('augeas') }
before(:each) do
augeas.stubs('close')
provider.aug = augeas
end
describe 'should find load errors' do
before(:each) do
augeas.expects(:match).with('/augeas//error').returns(['/augeas/files/foo/error'])
augeas.expects(:match).with('/augeas/files/foo/error/*').returns(['/augeas/files/foo/error/path', '/augeas/files/foo/error/message'])
augeas.expects(:get).with('/augeas/files/foo/error').returns('some_failure')
augeas.expects(:get).with('/augeas/files/foo/error/path').returns('/foo')
augeas.expects(:get).with('/augeas/files/foo/error/message').returns('Failed to...')
end
it 'and output only to debug when no path supplied' do
provider.expects(:debug).times(5)
provider.expects(:warning).never
provider.print_load_errors(nil)
end
it 'and output a warning and to debug when path supplied' do
augeas.expects(:match).with('/augeas/files/foo//error').returns(['/augeas/files/foo/error'])
provider.expects(:warning).once
provider.expects(:debug).times(4)
provider.print_load_errors('/augeas/files/foo//error')
end
it "and output only to debug when path doesn't match" do
augeas.expects(:match).with('/augeas/files/foo//error').returns([])
provider.expects(:warning).never
provider.expects(:debug).times(5)
provider.print_load_errors('/augeas/files/foo//error')
end
end
it 'finds load errors from lenses' do
augeas.expects(:match).with('/augeas//error').twice.returns(['/augeas/load/Xfm/error'])
augeas.expects(:match).with('/augeas/load/Xfm/error/*').returns([])
augeas.expects(:get).with('/augeas/load/Xfm/error').returns(['Could not find lens php.aug'])
provider.expects(:warning).once
provider.expects(:debug).twice
provider.print_load_errors('/augeas//error')
end
it 'finds save errors and output to debug' do
augeas.expects(:match).with("/augeas//error[. = 'put_failed']").returns(['/augeas/files/foo/error'])
augeas.expects(:match).with('/augeas/files/foo/error/*').returns(['/augeas/files/foo/error/path', '/augeas/files/foo/error/message'])
augeas.expects(:get).with('/augeas/files/foo/error').returns('some_failure')
augeas.expects(:get).with('/augeas/files/foo/error/path').returns('/foo')
augeas.expects(:get).with('/augeas/files/foo/error/message').returns('Failed to...')
provider.expects(:debug).times(5)
provider.print_put_errors
end
end
# Run initialisation tests of the real Augeas library to test our open_augeas
# method. This relies on Augeas and ruby-augeas on the host to be
# functioning.
describe 'augeas lib initialisation', if: Puppet.features.augeas? do
# Expect lenses for fstab and hosts
it 'has loaded standard files by default' do
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq(['/files/etc/fstab'])
expect(aug.match('/files/etc/hosts')).to eq(['/files/etc/hosts'])
expect(aug.match('/files/etc/test')).to eq([])
end
it 'reports load errors to debug only' do
provider.expects(:print_load_errors).with(nil)
aug = provider.open_augeas
expect(aug).not_to eq(nil)
end
# Only the file specified should be loaded
it 'loads one file if incl/lens used' do
resource[:incl] = '/etc/hosts'
resource[:lens] = 'Hosts.lns'
provider.expects(:print_load_errors).with('/augeas//error')
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq([])
expect(aug.match('/files/etc/hosts')).to eq(['/files/etc/hosts'])
expect(aug.match('/files/etc/test')).to eq([])
end
it 'alsoes load lenses from load_path' do
resource[:load_path] = my_fixture_dir
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq(['/files/etc/fstab'])
expect(aug.match('/files/etc/hosts')).to eq(['/files/etc/hosts'])
expect(aug.match('/files/etc/test')).to eq(['/files/etc/test'])
end
it "alsoes load lenses from pluginsync'd path" do
Puppet[:libdir] = my_fixture_dir
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq(['/files/etc/fstab'])
expect(aug.match('/files/etc/hosts')).to eq(['/files/etc/hosts'])
expect(aug.match('/files/etc/test')).to eq(['/files/etc/test'])
end
# Optimisations added for Augeas 0.8.2 or higher is available, see #7285
describe '>= 0.8.2 optimisations', if: Puppet.features.augeas? && Facter.value(:augeasversion) && Puppet::Util::Package.versioncmp(Facter.value(:augeasversion), '0.8.2') >= 0 do
it 'onlies load one file if relevant context given' do
resource[:context] = '/files/etc/fstab'
provider.expects(:print_load_errors).with('/augeas/files/etc/fstab//error')
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq(['/files/etc/fstab'])
expect(aug.match('/files/etc/hosts')).to eq([])
end
it 'onlies load one lens from load_path if context given' do
resource[:context] = '/files/etc/test'
resource[:load_path] = my_fixture_dir
provider.expects(:print_load_errors).with('/augeas/files/etc/test//error')
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq([])
expect(aug.match('/files/etc/hosts')).to eq([])
expect(aug.match('/files/etc/test')).to eq(['/files/etc/test'])
end
it "loads standard files if context isn't specific" do
resource[:context] = '/files/etc'
provider.expects(:print_load_errors).with(nil)
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq(['/files/etc/fstab'])
expect(aug.match('/files/etc/hosts')).to eq(['/files/etc/hosts'])
end
it 'does not optimise if the context is a complex path' do
resource[:context] = "/files/*[label()='etc']"
provider.expects(:print_load_errors).with(nil)
aug = provider.open_augeas
expect(aug).not_to eq(nil)
expect(aug.match('/files/etc/fstab')).to eq(['/files/etc/fstab'])
expect(aug.match('/files/etc/hosts')).to eq(['/files/etc/hosts'])
end
end
end
describe 'get_load_path' do
it 'offers no load_path by default' do
expect(provider.get_load_path(resource)).to eq('')
end
it 'offers one path from load_path' do
resource[:load_path] = '/foo'
expect(provider.get_load_path(resource)).to eq('/foo')
end
it 'offers multiple colon-separated paths from load_path' do
resource[:load_path] = '/foo:/bar:/baz'
expect(provider.get_load_path(resource)).to eq('/foo:/bar:/baz')
end
it 'offers multiple paths in array from load_path' do
resource[:load_path] = ['/foo', '/bar', '/baz']
expect(provider.get_load_path(resource)).to eq('/foo:/bar:/baz')
end
context 'when running application is agent' do
before(:each) do
Puppet[:libdir] = my_fixture_dir
Puppet.run_mode.stubs(:name).returns(:agent)
end
it 'offers pluginsync augeas/lenses subdir' do
expect(provider.get_load_path(resource)).to eq("#{my_fixture_dir}/augeas/lenses")
end
it 'offers both pluginsync and load_path paths' do
resource[:load_path] = ['/foo', '/bar', '/baz']
expect(provider.get_load_path(resource)).to eq("/foo:/bar:/baz:#{my_fixture_dir}/augeas/lenses")
end
end
context 'when running application is not agent' do
before(:each) do
Puppet.run_mode.stubs(:name).returns(:user)
env = Puppet::Node::Environment.create('root', ['/modules/foobar'])
Puppet.stubs(:lookup).returns(env)
env.stubs(:each_plugin_directory).yields('/modules/foobar')
resource[:load_path] = ['/foo', '/bar', '/baz']
end
it 'offers both load_path and module lenses path when available' do
File.stubs(:exist?).with('/modules/foobar/augeas/lenses').returns(true)
expect(provider.get_load_path(resource)).to eq('/foo:/bar:/baz:/modules/foobar/augeas/lenses')
end
it 'offers only load_path if module lenses path is not available' do
File.stubs(:exist?).with('/modules/foobar/augeas/lenses').returns(false)
expect(provider.get_load_path(resource)).to eq('/foo:/bar:/baz')
end
end
end
end
Copyright 2K16 - 2K18 Indonesian Hacker Rulez