/
opt
/
puppetlabs
/
puppet
/
vendor_modules
/
augeas_core
/
spec
/
unit
/
provider
/
augeas
/
Upload Filee
HOME
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) { instance_double('Augeas', get: 'value') } before(:each) do allow(augeas).to receive('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) { instance_double('Augeas', match: ['set', 'of', 'values']) } before(:each) do allow(augeas).to receive(:get).and_return('set', 'of', 'values') allow(augeas).to receive('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 allow(provider.aug).to receive(:match).and_return(['set', "o'values", 'here']) allow(provider.aug).to receive(:get).and_return('set', "o'values", '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 allow(provider.aug).to receive(:match).and_return(['set', 'o\\values', 'here']) allow(provider.aug).to receive(:get).and_return('set', 'o\\values', '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 allow(provider.aug).to receive(:match).and_return([]) allow(provider.aug).to receive(:get) command = ['values', 'fake value', '== []'] expect(provider.process_values(command)).to eq(true) end end describe 'match filters' do let(:augeas) { instance_double('Augeas', match: ['set', 'of', 'values']) } before(:each) do allow(augeas).to receive('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) { instance_double('Augeas') } before(:each) do allow(augeas).to receive('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 allow(provider).to receive(:get_augeas_version).and_return('0.3.5') end it 'handles no filters' do allow(augeas).to receive('match').and_return(['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' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('get').and_return('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.numeric?(nil)).to eq(false) end it 'returns true for Integers' do expect(provider.numeric?(9)).to eq(true) end it 'returns true for numbers in Strings' do expect(provider.numeric?('9')).to eq(true) end it 'returns false for non-number Strings' do expect(provider.numeric?('x9')).to eq(false) end it 'returns false for other types' do expect(provider.numeric?([true])).to eq(false) end end it 'returns false when a get filter does not match' do resource[:onlyif] = 'get path == another value' allow(augeas).to receive('get').and_return('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' allow(augeas).to receive('match').and_return(['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' allow(augeas).to receive('match').and_return(['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' allow(augeas).to receive('match').and_return(['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' allow(augeas).to receive('match').and_return(['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' allow(augeas).to receive('match').and_return(['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"] allow(File).to receive(:delete) allow(provider).to receive(:get_augeas_version).and_return('0.10.0') allow(provider).to receive('diff').with(file.to_s, "#{file}.augnew").and_return('diff') allow(augeas).to receive(:set).and_return(true) allow(augeas).to receive(:save).and_return(true) allow(augeas).to receive(:match).with('/augeas/events/saved').and_return(['/augeas/events/saved']) allow(augeas).to receive(:get).with('/augeas/events/saved').and_return("/files#{file}") allow(augeas).to receive(: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] = '' allow(provider).to receive(:get_augeas_version).and_return('0.10.0') allow(augeas).to receive(:set).and_return(true) allow(augeas).to receive(:save).and_return(true) end it 'displays a diff when a single file is shown to have been changed' do file = '/etc/hosts' allow(File).to receive(:delete) resource[:loglevel] = 'crit' resource[:context] = '/files' resource[:changes] = ["set #{file}/foo bar"] allow(augeas).to receive(:match).with('/augeas/events/saved').and_return(['/augeas/events/saved']) allow(augeas).to receive(:get).with('/augeas/events/saved').and_return("/files#{file}") expect(augeas).to receive(:set).with('/augeas/save', 'newfile') expect(provider).to receive('diff').with(file.to_s, "#{file}.augnew").and_return('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' allow(File).to receive(:delete) resource[:context] = '/files' resource[:changes] = ["set #{file1}/foo bar", "set #{file2}/baz biz"] allow(augeas).to receive(:match).with('/augeas/events/saved').and_return(['/augeas/events/saved[1]', '/augeas/events/saved[2]']) allow(augeas).to receive(:get).with('/augeas/events/saved[1]').and_return("/files#{file1}") allow(augeas).to receive(:get).with('/augeas/events/saved[2]').and_return("/files#{file2}") expect(augeas).to receive(:set).with('/augeas/save', 'newfile') expect(provider).to receive(:diff).with(file1.to_s, "#{file1}.augnew").and_return("diff #{file1}") expect(provider).to receive(:diff).with(file2.to_s, "#{file2}.augnew").and_return("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' allow(File).to receive(:delete) resource[:context] = '/files' resource[:changes] = ["set #{file}/foo bar"] resource[:root] = root allow(augeas).to receive(:match).with('/augeas/events/saved').and_return(['/augeas/events/saved']) allow(augeas).to receive(:get).with('/augeas/events/saved').and_return("/files#{file}") expect(augeas).to receive(:set).with('/augeas/save', 'newfile') expect(provider).to receive(:diff).with("#{root}#{file}", "#{root}#{file}.augnew").and_return('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"] allow(augeas).to receive(:match).with('/augeas/events/saved').and_return([]) expect(augeas).to receive(:set).with('/augeas/save', 'newfile') expect(augeas).to receive(:get).with('/augeas/events/saved').never expect(augeas).to receive(:close) expect(provider).to receive(: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"] allow(augeas).to receive(:match).with('/augeas/events/saved').and_return(['/augeas/events/saved']) allow(augeas).to receive(:get).with('/augeas/events/saved').and_return("/files#{file}") expect(augeas).to receive(:set).with('/augeas/save', 'newfile') expect(augeas).to receive(:close) expect(File).to receive(:delete).with(file + '.augnew') expect(provider).to receive(:diff).with(file.to_s, "#{file}.augnew").and_return('') 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"] allow(augeas).to receive(:match).with('/augeas/events/saved').and_return(['/augeas/events/saved[1]', '/augeas/events/saved[2]']) allow(augeas).to receive(:get).with('/augeas/events/saved[1]').and_return("/files#{file}") allow(augeas).to receive(:get).with('/augeas/events/saved[2]').and_return("/files#{file}") expect(augeas).to receive(:set).with('/augeas/save', 'newfile') expect(augeas).to receive(:close) expect(File).to receive(:delete).with(file + '.augnew').once expect(provider).to receive(:diff).with(file.to_s, "#{file}.augnew").and_return('').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"] allow(augeas).to receive(:save).and_return(false) allow(augeas).to receive(:match).with('/augeas/events/saved').and_return([]) expect(augeas).to receive(:close) expect(provider).to receive(:diff).never expect(provider).to receive(:print_put_errors) expect { provider.need_to_run? }.to raise_error(Puppet::Error) end end end describe 'augeas execution integration' do let(:augeas) { instance_double('Augeas', load: nil) } before(:each) do allow(augeas).to receive('close') allow(augeas).to receive(:match).with('/augeas/events/saved').and_return([]) provider.aug = augeas allow(provider).to receive(:get_augeas_version).and_return('0.3.5') end it 'handles set commands' do resource[:changes] = 'set JarJar Binks' resource[:context] = '/some/path/' expect(augeas).to receive(:set).with('/some/path/JarJar', 'Binks').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles rm commands' do resource[:changes] = 'rm /Jar/Jar' expect(augeas).to receive(:rm).with('/Jar/Jar') expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles remove commands' do resource[:changes] = 'remove /Jar/Jar' expect(augeas).to receive(:rm).with('/Jar/Jar') expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles clear commands' do resource[:changes] = 'clear Jar/Jar' resource[:context] = '/foo/' expect(augeas).to receive(:clear).with('/foo/Jar/Jar').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:match).with('/foo/Jar/Jar').and_return([]) expect(augeas).to receive(:clear).with('/foo/Jar/Jar').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:match).with('/foo/Jar/Jar').and_return(['/foo/Jar/Jar']) expect(augeas).to receive(:clear).never expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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' expect(augeas).to receive(:insert).with('/foo/Jar/Jar', 'Binks', true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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' expect(augeas).to receive(:insert).with('/Jar/Jar', 'Binks', false) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles ins with no context' do resource[:changes] = 'ins Binks after /Jar/Jar' expect(augeas).to receive(:insert).with('/Jar/Jar', 'Binks', false) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:insert).with('/Jar/Jar', 'Binks', false) expect(augeas).to receive(:clear).with('/foo/Jar/Jar').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles defvar commands' do resource[:changes] = 'defvar myjar Jar/Jar' resource[:context] = '/foo/' expect(augeas).to receive(:defvar).with('myjar', '/foo/Jar/Jar').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:defvar).with('myjar', '/foo/Jar/Jar').and_return(true) # this is the important bit, shouldn't be /foo/$myjar/Binks expect(augeas).to receive(:set).with('$myjar/Binks', '1').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:defnode).with('newjar', '/foo/Jar/Jar[last()+1]', 'Binks').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles mv commands' do resource[:changes] = 'mv Jar/Jar Binks' resource[:context] = '/foo/' expect(augeas).to receive(:mv).with('/foo/Jar/Jar', '/foo/Binks').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(:close) expect(provider.execute_changes).to eq(:executed) end it 'handles rename commands' do resource[:changes] = 'rename Jar/Jar Binks' resource[:context] = '/foo/' expect(augeas).to receive(:rename).with('/foo/Jar/Jar', 'Binks').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:respond_to?).with('setm').and_return(true) expect(augeas).to receive(:set).with('/foo/test[1]/Jar/Jar', 'Foo').and_return(true) expect(augeas).to receive(:set).with('/foo/test[2]/Jar/Jar', 'Bar').and_return(true) expect(augeas).to receive(:setm).with('/foo/test', 'Jar/Jar', 'Binks').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:respond_to?).with('setm').and_return(false) expect(augeas).to receive(:set).with('/foo/test[1]/Jar/Jar', 'Foo').and_return(true) expect(augeas).to receive(:set).with('/foo/test[2]/Jar/Jar', 'Bar').and_return(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/' expect(augeas).to receive(:respond_to?).with('clearm').and_return(true) expect(augeas).to receive(:set).with('/foo/test[1]/Jar/Jar', 'Foo').and_return(true) expect(augeas).to receive(:set).with('/foo/test[2]/Jar/Jar', 'Bar').and_return(true) expect(augeas).to receive(:clearm).with('/foo/test', 'Jar/Jar').and_return(true) expect(augeas).to receive(:save).and_return(true) expect(augeas).to receive(: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/' expect(augeas).to receive(:respond_to?).with('clearm').and_return(false) expect(augeas).to receive(:set).with('/foo/test[1]/Jar/Jar', 'Foo').and_return(true) expect(augeas).to receive(:set).with('/foo/test[2]/Jar/Jar', 'Bar').and_return(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/' expect(augeas).to receive(:respond_to?).with('clearm').and_return(true) expect(augeas).to receive(:set).with('/foo/test[1]/Jar/Jar', 'Foo').and_return(true) expect(augeas).to receive(:set).with('/foo/test[2]/Jar/Jar', 'Bar').and_return(true) expect(augeas).to receive(:clearm).with('/foo/test', 'Jar/Jar').and_return(true) expect(augeas).to receive(:save).and_return(false) expect(provider).to receive(:print_put_errors) expect(augeas).to receive(:match).and_return([]) 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 allow(Puppet::Util::Storage).to receive(: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) { instance_double('Augeas') } before(:each) do allow(augeas).to receive('close') provider.aug = augeas end describe 'should find load errors' do before(:each) do allow(augeas).to receive(:match).with('/augeas//error').and_return(['/augeas/files/foo/error']) allow(augeas).to receive(:match).with('/augeas/files/foo/error/*').and_return(['/augeas/files/foo/error/path', '/augeas/files/foo/error/message']) allow(augeas).to receive(:get).with('/augeas/files/foo/error').and_return('some_failure') allow(augeas).to receive(:get).with('/augeas/files/foo/error/path').and_return('/foo') allow(augeas).to receive(:get).with('/augeas/files/foo/error/message').and_return('Failed to...') end it 'and output only to debug when no path supplied' do expect(provider).to receive(:debug).exactly(5).times expect(provider).to receive(:warning).never provider.print_load_errors(nil) end it 'and output a warning and to debug when path supplied' do expect(augeas).to receive(:match).with('/augeas/files/foo//error').and_return(['/augeas/files/foo/error']) expect(provider).to receive(:warning).once expect(provider).to receive(:debug).exactly(4).times provider.print_load_errors('/augeas/files/foo//error') end it "and output only to debug when path doesn't match" do expect(augeas).to receive(:match).with('/augeas/files/foo//error').and_return([]) expect(provider).to receive(:warning).never expect(provider).to receive(:debug).exactly(5).times provider.print_load_errors('/augeas/files/foo//error') end end it 'finds load errors from lenses' do expect(augeas).to receive(:match).with('/augeas//error').twice.and_return(['/augeas/load/Xfm/error']) expect(augeas).to receive(:match).with('/augeas/load/Xfm/error/*').and_return([]) expect(augeas).to receive(:get).with('/augeas/load/Xfm/error').and_return(['Could not find lens php.aug']) expect(provider).to receive(:warning).once expect(provider).to receive(:debug).twice provider.print_load_errors('/augeas//error') end it 'finds save errors and output to debug' do expect(augeas).to receive(:match).with("/augeas//error[. = 'put_failed']").and_return(['/augeas/files/foo/error']) expect(augeas).to receive(:match).with('/augeas/files/foo/error/*').and_return(['/augeas/files/foo/error/path', '/augeas/files/foo/error/message']) expect(augeas).to receive(:get).with('/augeas/files/foo/error').and_return('some_failure') expect(augeas).to receive(:get).with('/augeas/files/foo/error/path').and_return('/foo') expect(augeas).to receive(:get).with('/augeas/files/foo/error/message').and_return('Failed to...') expect(provider).to receive(:debug).exactly(5).times 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 expect(provider).to receive(: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' expect(provider).to receive(: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' expect(provider).to receive(: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 expect(provider).to receive(: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' expect(provider).to receive(: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']" expect(provider).to receive(: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 allow(Puppet.run_mode).to receive(:name).and_return(: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 allow(Puppet.run_mode).to receive(:name).and_return(:user) env = Puppet::Node::Environment.create('root', ['/modules/foobar']) allow(Puppet).to receive(:lookup).and_return(env) allow(env).to receive(:each_plugin_directory).and_yield('/modules/foobar') resource[:load_path] = ['/foo', '/bar', '/baz'] end it 'offers both load_path and module lenses path when available' do allow(File).to receive(:exist?).with('/modules/foobar/augeas/lenses').and_return(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 allow(File).to receive(:exist?).with('/modules/foobar/augeas/lenses').and_return(false) expect(provider.get_load_path(resource)).to eq('/foo:/bar:/baz') end end end end