From 54ffdd85471bba36752aa6ad036c238744283632 Mon Sep 17 00:00:00 2001 From: Hunter Haugen Date: Wed, 11 Oct 2017 14:32:28 -0700 Subject: [PATCH] (MODULES-5651) Do not append infinitely https://tickets.puppetlabs.com/browse/MODULES-5003 gave rise to https://github.com/puppetlabs/puppetlabs-stdlib/pull/788 and https://github.com/puppetlabs/puppetlabs-stdlib/pull/794 which caused different behavior based on whether the line value was matched by the match regex or not. The change in behavior was both breaking and broken, though that was hard to tell because the behavior was ill-described in general. [bugfix] This commit resolves the breaking behavior by reverting the behavior of "replacing matches when a line matching `line` exists even when `multiple` is set to `true`". [feature] This commit adds a new parameter to make file_line replace all matches universally with the `line` value, even when the line exists elsewhere in the file. This feature only affects modifying multiple lines in a file when the `line` value already exists. [bugfix] This commit more strictly defines the various interactions of `ensure`, `match`, `append_on_no_match`, `replace`, `multiple`, and `replace_all_matches_not_matching_line`. It also more clearly documents and tests these interactions. --- README.md | 14 +- lib/puppet/provider/file_line/ruby.rb | 59 +- lib/puppet/type/file_line.rb | 13 + .../puppet/provider/file_line/ruby_spec.rb | 739 ++++++++++++------ 4 files changed, 555 insertions(+), 270 deletions(-) diff --git a/README.md b/README.md index 2b6a64bd9..b6d6c0dd2 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Values: String. ##### `match` -Specifies a regular expression to compare against existing lines in the file; if a match is found, it is replaced rather than adding a new line. A regex comparison is performed against the line value, and if it does not match, an exception is raised. +Specifies a regular expression to compare against existing lines in the file; if a match is found, it is replaced rather than adding a new line. Values: String containing a regex. @@ -236,7 +236,7 @@ Default value: `false`. ##### `multiple` -Specifies whether `match` and `after` can change multiple lines. If set to `false`, an exception is raised if more than one line matches. +Specifies whether `match` and `after` can change multiple lines. If set to `false`, allows file\_line to replace only one line and raises an error if more than one will be replaced. If set to `true`, allows file\_line to replace one or more lines. Values: `true`, `false`. @@ -261,12 +261,20 @@ Value: String specifying an absolute path to the file. ##### `replace` -Specifies whether the resource overwrites an existing line that matches the `match` parameter. If set to `false` and a line is found matching the `match` parameter, the line is not placed in the file. +Specifies whether the resource overwrites an existing line that matches the `match` parameter when `line` does not otherwise exist. + +If set to `false` and a line is found matching the `match` parameter, the line is not placed in the file. Boolean. Default value: `true`. +##### `replace_all_matches_not_matching_line` + +Replace all lines matched by `match` parameter, even if `line` already exists in the file. + +Default value: `false`. + ### Data types #### `Stdlib::Absolutepath` diff --git a/lib/puppet/provider/file_line/ruby.rb b/lib/puppet/provider/file_line/ruby.rb index 16f2709c3..2d188e050 100644 --- a/lib/puppet/provider/file_line/ruby.rb +++ b/lib/puppet/provider/file_line/ruby.rb @@ -12,12 +12,50 @@ def exists? found = lines_count > 0 else match_count = count_matches(new_match_regex) - if resource[:append_on_no_match].to_s == 'false' - found = true - elsif resource[:replace].to_s == 'true' - found = lines_count > 0 && lines_count == match_count + if resource[:ensure] == :present + if match_count == 0 + if lines_count == 0 + if resource[:append_on_no_match].to_s == 'false' + found = true # lies, but gets the job done + else + found = false + end + else + found = true + end + else + if resource[:replace_all_matches_not_matching_line].to_s == 'true' + found = false # maybe lies, but knows there's still work to do + else + if lines_count == 0 + if resource[:replace].to_s == 'false' + found = true + else + found = false + end + else + found = true + end + end + end else - found = match_count > 0 + if match_count == 0 + if lines_count == 0 + found = false + else + found = true + end + else + if lines_count == 0 + if resource[:match_for_absence].to_s == 'true' + found = true # found matches, not lines + else + found = false + end + else + found = true + end + end end end found @@ -68,7 +106,13 @@ def new_match_regex end def count_matches(regex) - lines.select{ |line| line.match(regex) }.size + lines.select do |line| + if resource[:replace_all_matches_not_matching_line].to_s == 'true' + line.match(regex) unless line.chomp == resource[:line] + else + line.match(regex) + end + end.size end def handle_create_with_match() @@ -140,8 +184,9 @@ def handle_destroy_line end def handle_append_line + local_lines = lines File.open(resource[:path],'w') do |fh| - lines.each do |line| + local_lines.each do |line| fh.puts(line) end fh.puts(resource[:line]) diff --git a/lib/puppet/type/file_line.rb b/lib/puppet/type/file_line.rb index 06be5522d..42d7d5fa9 100644 --- a/lib/puppet/type/file_line.rb +++ b/lib/puppet/type/file_line.rb @@ -151,6 +151,13 @@ def retrieve defaultto true end + newparam(:replace_all_matches_not_matching_line) do + desc 'Configures the behavior of replacing all lines in a file which match the `match` parameter regular expression, regardless of whether the specified line is already present in the file.' + + newvalues(true, false) + defaultto false + end + newparam(:encoding) do desc 'For files that are not UTF-8 encoded, specify encoding such as iso-8859-1' defaultto 'UTF-8' @@ -168,6 +175,12 @@ def retrieve end validate do + if self[:replace_all_matches_not_matching_line].to_s == 'true' and self[:multiple].to_s == 'false' + raise(Puppet::Error, "multiple must be true when replace_all_matches_not_matching_line is true") + end + if self[:replace_all_matches_not_matching_line].to_s == 'true' and self[:replace].to_s == 'false' + raise(Puppet::Error, "replace must be true when replace_all_matches_not_matching_line is true") + end unless self[:line] unless (self[:ensure].to_s == 'absent') and (self[:match_for_absence].to_s == 'true') and self[:match] raise(Puppet::Error, "line is a required attribute") diff --git a/spec/unit/puppet/provider/file_line/ruby_spec.rb b/spec/unit/puppet/provider/file_line/ruby_spec.rb index 4ec9baed7..0330f1d0a 100755 --- a/spec/unit/puppet/provider/file_line/ruby_spec.rb +++ b/spec/unit/puppet/provider/file_line/ruby_spec.rb @@ -1,159 +1,370 @@ #! /usr/bin/env ruby -S rspec require 'spec_helper' -require 'tempfile' + provider_class = Puppet::Type.type(:file_line).provider(:ruby) # These tests fail on windows when run as part of the rake task. Individually they pass describe provider_class, :unless => Puppet::Util::Platform.windows? do - context "when adding" do - let :tmpfile do - tmp = Tempfile.new('tmp') - path = tmp.path - tmp.close! - path + include PuppetlabsSpec::Files + + let :tmpfile do + tmpfilename("file_line_test") + end + let :content do + '' + end + let :params do + { } + end + let :resource do + Puppet::Type::File_line.new( { + name: 'foo', + path: tmpfile, + line: 'foo', + }.merge(params)) + end + let :provider do + provider_class.new(resource) + end + before :each do + File.open(tmpfile, 'w') do |fh| + fh.write(content) end - let :resource do - Puppet::Type::File_line.new( - {:name => 'foo', :path => tmpfile, :line => 'foo'} - ) + end + + describe "line parameter" do + context "line exists" do + let(:content) { 'foo' } + it 'detects the line' do + expect(provider.exists?).to be_truthy + end end - let :provider do - provider_class.new(resource) + context "line does not exist" do + let(:content) { 'foo bar' } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'appends the line' do + provider.create + expect(File.read(tmpfile).chomp).to eq("foo bar\nfoo") + end end - - it 'should detect if the line exists in the file' do - File.open(tmpfile, 'w') do |fh| - fh.write('foo') + end + describe "match parameter" do + context "does not match line" do + let(:params) { { match: '^bar' } } + context "line does not exist" do + describe "replacing" do + let(:content) { "foo bar\nbar" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the match' do + provider.create + expect(File.read(tmpfile).chomp).to eq("foo bar\nfoo") + end + end + describe "appending" do + let(:params) { super().merge({ replace: false }) } + let(:content) { "foo bar\nbar" } + it 'does not request changes' do + expect(provider.exists?).to be_truthy + end + end + end + context "line exists" do + let(:content) { "foo\nbar" } + it 'detects the line' do + expect(provider.exists?).to be_truthy + end end - expect(provider.exists?).to be_truthy end - it 'should detect if the line does not exist in the file' do - File.open(tmpfile, 'w') do |fh| - fh.write('foo1') + context "matches line" do + let(:params) { { match: '^foo' } } + context "line exists" do + let(:content) { "foo\nbar" } + it 'detects the line' do + expect(provider.exists?).to be_truthy + end + end + context "line does not exist" do + let(:content) { "foo bar\nbar" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the match' do + provider.create + expect(File.read(tmpfile).chomp).to eq("foo\nbar") + end + end + end + end + describe 'append_on_no_match' do + let(:params) { { + append_on_no_match: false, + match: '^foo1$', + } } + context 'when matching' do + let(:content) { "foo1\nbar" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the match' do + provider.create + expect(File.read(tmpfile).chomp).to eql("foo\nbar") end - expect(provider.exists?).to eql (false) end - it 'should append to an existing file when creating' do - provider.create - expect(File.read(tmpfile).chomp).to eq('foo') + context 'when not matching' do + let(:content) { "foo3\nbar" } + it 'does not affect the file' do + expect(provider.exists?).to be_truthy + end end end - context 'when using replace' 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*=.*$', - :replace => false, - } - ) - @provider = provider_class.new(@resource) + describe 'replace_all_matches_not_matching_line' do + context 'when replace is false' do + let(:params) { { + replace_all_matches_not_matching_line: true, + replace: false, + } } + it 'raises an error' do + expect { provider.exists? }.to raise_error(Puppet::Error, /replace must be true/) + end end - - it 'should not replace the matching line' do - File.open(@tmpfile, 'w') do |fh| - fh.write("foo1\nfoo=blah\nfoo2\nfoo3") + context 'when match matches line' do + let(:params) { { + replace_all_matches_not_matching_line: true, + match: '^foo', + multiple: true, + } } + context 'when there are more matches than lines' do + let(:content) { "foo\nfoo bar\nbar\nfoo baz" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the matches' do + provider.create + expect(File.read(tmpfile).chomp).to eql("foo\nfoo\nbar\nfoo") + end + end + context 'when there are the same matches and lines' do + let(:content) { "foo\nfoo\nbar" } + it 'does not request changes' do + expect(provider.exists?).to be_truthy + end end - expect(@provider.exists?).to be_truthy - @provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo=blah\nfoo2\nfoo3") end - - it 'should append the line if no matches are found' do - File.open(@tmpfile, 'w') do |fh| - fh.write("foo1\nfoo2") + context 'when match does not match line' do + let(:params) { { + replace_all_matches_not_matching_line: true, + match: '^bar', + multiple: true, + } } + context 'when there are more matches than lines' do + let(:content) { "foo\nfoo bar\nbar\nbar baz" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the matches' do + provider.create + expect(File.read(tmpfile).chomp).to eql("foo\nfoo bar\nfoo\nfoo") + end + end + context 'when there are the same matches and lines' do + let(:content) { "foo\nfoo\nbar\nbar baz" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the matches' do + provider.create + expect(File.read(tmpfile).chomp).to eql("foo\nfoo\nfoo\nfoo") + end + end + context 'when there are no matches' do + let(:content) { "foo\nfoo bar" } + it 'does not request changes' do + expect(provider.exists?).to be_truthy + end + end + context 'when there are no matches or lines' do + let(:content) { "foo bar" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'appends the line' do + provider.create + expect(File.read(tmpfile).chomp).to eql("foo bar\nfoo") + end end - expect(@provider.exists?).to eql (false) - @provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo2\nfoo = bar") end + end + describe 'match_for_absence' do + end + describe 'customer use cases' do + describe 'MODULES-5003' do + let(:params) { { + line: "*\thard\tcore\t0", + match: "^[ \t]*\\*[ \t]+hard[ \t]+core[ \t]+.*", + multiple: true, + } } + context 'no lines' do + let(:content) { "* hard core 90\n* hard core 10\n" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the matches' do + provider.create + expect(File.read(tmpfile).chomp).to eq("* hard core 0\n* hard core 0") + end + end + context 'one match, one line' do + let(:content) { "* hard core 90\n* hard core 0\n" } + describe 'just ensure the line exists' do + it 'does not request changes' do + expect(provider.exists?).to be_truthy + end + end + describe 'replace all matches, even when line exists' do + let(:params) { super().merge(replace_all_matches_not_matching_line: true) } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the matches' do + provider.create + expect(File.read(tmpfile).chomp).to eq("* hard core 0\n* hard core 0") + end + end + end + end + describe 'MODULES-5651' do + let(:params) { { + line: 'LogLevel=notice', + match: '^#LogLevel$', + } } + context "match, no line" do + let(:content) { "#LogLevel\nstuff" } + it 'requests changes' do + expect(provider.exists?).to be_falsy + end + it 'replaces the match' do + provider.create + expect(File.read(tmpfile).chomp).to eq("LogLevel=notice\nstuff") + end + end + context "match, line" do + let(:content) { "#Loglevel\nLogLevel=notice\nstuff" } + it 'does not request changes' do + expect(provider.exists?).to be_truthy + end + end + context "no match, line" do + let(:content) { "LogLevel=notice\nstuff" } + it 'does not request changes' do + expect(provider.exists?).to be_truthy + end + end + end + end + describe "#create" do + context "when adding" do + end + context 'when replacing' do + let :params do + { + line: 'foo = bar', + match: '^foo\s*=.*$', + replace: false, + } + end + let(:content) { "foo1\nfoo=blah\nfoo2\nfoo3" } - it 'should raise an error with invalid values' do - expect { - @resource = Puppet::Type::File_line.new( - { - :name => 'foo', - :path => @tmpfile, - :line => 'foo = bar', - :match => '^foo\s*=.*$', - :replace => 'asgadga', - } - ) - }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./) + it 'should not replace the matching line' do + expect(provider.exists?).to be_truthy + provider.create + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo=blah\nfoo2\nfoo3") + end + it 'should append the line if no matches are found' do + File.open(tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2") + end + expect(provider.exists?).to eql (false) + provider.create + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo2\nfoo = bar") + end + it 'should raise an error with invalid values' do + expect { + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :replace => 'asgadga', + } + ) + }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./) + end end end + describe "#destroy" do + end 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, + :path => tmpfile, :line => 'foo = bar', :match => '^foo\s*=.*$', } ) @provider = provider_class.new(@resource) end - describe 'using match' do 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| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") end expect(@provider.exists?).to eql(false) expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/) - expect(File.read(@tmpfile)).to eql("foo1\nfoo=blah\nfoo2\nfoo=baz") + expect(File.read(tmpfile)).to eql("foo1\nfoo=blah\nfoo2\nfoo=baz") end it 'should replace all lines that matches' do @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :line => 'foo = bar', :match => '^foo\s*=.*$', :multiple => true, } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") end expect(@provider.exists?).to eql(false) @provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2\nfoo = bar") + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2\nfoo = bar") end it 'should replace all lines that match, even when some lines are correct' do @resource = Puppet::Type::File_line.new( { :name => 'neil', - :path => @tmpfile, + :path => tmpfile, :line => "\thard\tcore\t0\n", :match => '^[ \t]hard[ \t]+core[ \t]+.*', :multiple => true, } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("\thard\tcore\t90\n\thard\tcore\t0\n") end expect(@provider.exists?).to eql(false) @provider.create - expect(File.read(@tmpfile).chomp).to eql("\thard\tcore\t0\n\thard\tcore\t0") + expect(File.read(tmpfile).chomp).to eql("\thard\tcore\t0\n\thard\tcore\t0") end it 'should raise an error with invalid values' do @@ -161,7 +372,7 @@ @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :line => 'foo = bar', :match => '^foo\s*=.*$', :multiple => 'asgadga', @@ -171,184 +382,202 @@ end it 'should replace a line that matches' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo=blah\nfoo2") end expect(@provider.exists?).to eql(false) @provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") end it 'should add a new line if no lines match' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo2") end expect(@provider.exists?).to eql(false) @provider.create - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo = bar\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\nfoo = bar\n") end it 'should do nothing if the exact line already exists' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo = bar\nfoo2") end expect(@provider.exists?).to eql(true) @provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") end - it 'should not add line after no matches found' do - @resource = Puppet::Type::File_line.new( - { - :name => 'foo', - :path => @tmpfile, - :line => 'inserted = line', - :match => '^foo3$', - :append_on_no_match => false, - } - ) - @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| - fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") - end - expect(@provider.exists?).to be true - expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = blah\nfoo2\nfoo = baz") - end end - - describe 'using after' do - let :resource do - Puppet::Type::File_line.new( - { - :name => 'foo', - :path => @tmpfile, - :line => 'inserted = line', - :after => '^foo1', - } - ) - end - - let :provider do - provider_class.new(resource) - end - context 'match and after set' do - shared_context 'resource_create' do - let(:match) { '^foo2$' } - let(:after) { '^foo1$' } - let(:resource) { - Puppet::Type::File_line.new( - { - :name => 'foo', - :path => @tmpfile, - :line => 'inserted = line', - :after => after, - :match => match, - } - ) - } - end - before :each do - File.open(@tmpfile, 'w') do |fh| - fh.write("foo1\nfoo2\nfoo = baz") - end - end - describe 'inserts at match' do - include_context 'resource_create' - it { - provider.create - expect(File.read(@tmpfile).chomp).to eq("foo1\ninserted = line\nfoo = baz") - } - end - describe 'inserts a new line after when no match' do - include_context 'resource_create' do - let(:match) { '^nevergoingtomatch$' } - end - it { - provider.create - expect(File.read(@tmpfile).chomp).to eq("foo1\ninserted = line\nfoo2\nfoo = baz") - } - end - describe 'append to end of file if no match for both after and match' do - include_context 'resource_create' do - let(:match) { '^nevergoingtomatch$' } - let(:after) { '^stillneverafter' } + describe 'using match+append_on_no_match' do + context 'when there is a match' do + it 'should replace line' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => tmpfile, + :line => 'inserted = line', + :match => '^foo3$', + :append_on_no_match => false, + } + ) + @provider = provider_class.new(@resource) + File.open(tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") end - it { - provider.create - expect(File.read(@tmpfile).chomp).to eq("foo1\nfoo2\nfoo = baz\ninserted = line") - } + expect(@provider.exists?).to be true + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo = blah\nfoo2\nfoo = baz") end end - context 'with one line matching the after expression' do - before :each do - File.open(@tmpfile, 'w') do |fh| + context 'when there is no match' do + it 'should not add line after no matches found' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => tmpfile, + :line => 'inserted = line', + :match => '^foo3$', + :append_on_no_match => false, + } + ) + @provider = provider_class.new(@resource) + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") end + expect(@provider.exists?).to be true + expect(File.read(tmpfile).chomp).to eql("foo1\nfoo = blah\nfoo2\nfoo = baz") end + end + end + end + context "when match+replace+append_on_no_match" do + end + context 'when after' do + let :resource do + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => tmpfile, + :line => 'inserted = line', + :after => '^foo1', + } + ) + end - it 'inserts the specified line after the line matching the "after" expression' do + let :provider do + provider_class.new(resource) + end + context 'match and after set' do + shared_context 'resource_create' do + let(:match) { '^foo2$' } + let(:after) { '^foo1$' } + let(:resource) { + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => tmpfile, + :line => 'inserted = line', + :after => after, + :match => match, + } + ) + } + end + before :each do + File.open(tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2\nfoo = baz") + end + end + describe 'inserts at match' do + include_context 'resource_create' + it { provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo = baz") + expect(File.read(tmpfile).chomp).to eq("foo1\ninserted = line\nfoo = baz") + } + end + describe 'inserts a new line after when no match' do + include_context 'resource_create' do + let(:match) { '^nevergoingtomatch$' } end + it { + provider.create + expect(File.read(tmpfile).chomp).to eq("foo1\ninserted = line\nfoo2\nfoo = baz") + } end - - context 'with multiple lines matching the after expression' do - before :each do - File.open(@tmpfile, 'w') do |fh| - fh.write("foo1\nfoo = blah\nfoo2\nfoo1\nfoo = baz") - end + describe 'append to end of file if no match for both after and match' do + include_context 'resource_create' do + let(:match) { '^nevergoingtomatch$' } + let(:after) { '^stillneverafter' } end - - it 'errors out stating "One or no line must match the pattern"' do - expect { provider.create }.to raise_error(Puppet::Error, /One or no line must match the pattern/) + it { + provider.create + expect(File.read(tmpfile).chomp).to eq("foo1\nfoo2\nfoo = baz\ninserted = line") + } + end + end + context 'with one line matching the after expression' do + before :each do + File.open(tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") end + end - it 'adds the line after all lines matching the after expression' do - @resource = Puppet::Type::File_line.new( - { - :name => 'foo', - :path => @tmpfile, - :line => 'inserted = line', - :after => '^foo1$', - :multiple => true, - } - ) - @provider = provider_class.new(@resource) - expect(@provider.exists?).to eql (false) - @provider.create - expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo1\ninserted = line\nfoo = baz") + it 'inserts the specified line after the line matching the "after" expression' do + provider.create + expect(File.read(tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo = baz") + end + end + context 'with multiple lines matching the after expression' do + before :each do + File.open(tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo1\nfoo = baz") end end - context 'with no lines matching the after expression' do - let :content do - "foo3\nfoo = blah\nfoo2\nfoo = baz\n" - end + it 'errors out stating "One or no line must match the pattern"' do + expect { provider.create }.to raise_error(Puppet::Error, /One or no line must match the pattern/) + end - before :each do - File.open(@tmpfile, 'w') do |fh| - fh.write(content) - end - end + it 'adds the line after all lines matching the after expression' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => tmpfile, + :line => 'inserted = line', + :after => '^foo1$', + :multiple => true, + } + ) + @provider = provider_class.new(@resource) + expect(@provider.exists?).to eql (false) + @provider.create + expect(File.read(tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo1\ninserted = line\nfoo = baz") + end + end + context 'with no lines matching the after expression' do + let :content do + "foo3\nfoo = blah\nfoo2\nfoo = baz\n" + end - it 'appends the specified line to the file' do - provider.create - expect(File.read(@tmpfile)).to eq(content << resource[:line] << "\n") + before :each do + File.open(tmpfile, 'w') do |fh| + fh.write(content) end end + + it 'appends the specified line to the file' do + provider.create + expect(File.read(tmpfile)).to eq(content << resource[:line] << "\n") + end end end - context "when removing with a line" 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, + :path => tmpfile, :line => 'foo', :ensure => 'absent', } @@ -356,59 +585,49 @@ @provider = provider_class.new(@resource) end it 'should remove the line if it exists' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + expect(File.read(tmpfile)).to eql("foo1\nfoo2") end - it 'should remove the line without touching the last new line' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2\n") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\n") end - it 'should remove any occurence of the line' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\n") end - it 'example in the docs' do @resource = Puppet::Type::File_line.new( { :name => 'bashrc_proxy', :ensure => 'absent', - :path => @tmpfile, + :path => tmpfile, :line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo2\nexport HTTP_PROXY=http://squid.puppetlabs.vm:3128\nfoo4\n") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo4\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\nfoo4\n") end end - context "when removing with a match" 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, + :path => tmpfile, :line => 'foo2', :ensure => 'absent', :match => 'o$', @@ -419,57 +638,57 @@ end it 'should find a line to match' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end expect(@provider.exists?).to eql (true) end it 'should remove one line if it matches' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + expect(File.read(tmpfile)).to eql("foo1\nfoo2") end it 'the line parameter is actually not used at all but is silently ignored if here' do @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :line => 'supercalifragilisticexpialidocious', :ensure => 'absent', :match => 'o$', :match_for_absence => true, } ) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + expect(File.read(tmpfile)).to eql("foo1\nfoo2") end it 'and may not be here and does not need to be here' do @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :ensure => 'absent', :match => 'o$', :match_for_absence => true, } ) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + expect(File.read(tmpfile)).to eql("foo1\nfoo2") end it 'should raise an error if more than one line matches' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") end expect { @provider.destroy }.to raise_error(Puppet::Error, /More than one line/) @@ -479,7 +698,7 @@ @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :line => 'foo2', :ensure => 'absent', :match => 'o$', @@ -488,36 +707,36 @@ } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\n") end it 'should ignore the match if match_for_absence is not specified' do @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :line => 'foo2', :ensure => 'absent', :match => 'o$', } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo\n") end it 'should ignore the match if match_for_absence is false' do @resource = Puppet::Type::File_line.new( { :name => 'foo', - :path => @tmpfile, + :path => tmpfile, :line => 'foo2', :ensure => 'absent', :match => 'o$', @@ -525,11 +744,11 @@ } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo\nfoo2") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo\n") end it 'example in the docs' do @@ -537,18 +756,18 @@ { :name => 'bashrc_proxy', :ensure => 'absent', - :path => @tmpfile, + :path => tmpfile, :line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', :match => '^export\ HTTP_PROXY\=', :match_for_absence => true, } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo2\nexport HTTP_PROXY=foo\nfoo4\n") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo4\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\nfoo4\n") end it 'example in the docs showing line is redundant' do @@ -556,17 +775,17 @@ { :name => 'bashrc_proxy', :ensure => 'absent', - :path => @tmpfile, + :path => tmpfile, :match => '^export\ HTTP_PROXY\=', :match_for_absence => true, } ) @provider = provider_class.new(@resource) - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write("foo1\nfoo2\nexport HTTP_PROXY=foo\nfoo4\n") end @provider.destroy - expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo4\n") + expect(File.read(tmpfile)).to eql("foo1\nfoo2\nfoo4\n") end end end