diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index 997206ec4..cd188fafd 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -1,60 +1,67 @@ require 'puppet' +require 'fileutils' +require 'tempfile' Puppet::Reports.register_report(:store) do desc "Store the yaml report on disk. Each host sends its report as a YAML dump and this just stores the file on disk, in the `reportdir` directory. These files collect quickly -- one every half hour -- so it is a good idea to perform some maintenance on them if you use this report (it's the only default report)." def process # We don't want any tracking back in the fs. Unlikely, but there # you go. client = self.host.gsub("..",".") dir = File.join(Puppet[:reportdir], client) if ! FileTest.exists?(dir) FileUtils.mkdir_p(dir) FileUtils.chmod_R(0750, dir) end # Now store the report. now = Time.now.gmtime name = %w{year month day hour min}.collect do |method| # Make sure we're at least two digits everywhere "%02d" % now.send(method).to_s end.join("") + ".yaml" file = File.join(dir, name) + f = Tempfile.new(name, dir) begin - File.open(file, "w", 0640) do |f| + begin + f.chmod(0640) f.print to_yaml + ensure + f.close end + FileUtils.mv(f.path, file) rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.warning "Could not write report for #{client} at #{file}: #{detail}" end # Only testing cares about the return value file end # removes all reports for a given host def self.destroy(host) client = host.gsub("..",".") dir = File.join(Puppet[:reportdir], client) if File.exists?(dir) Dir.entries(dir).each do |file| next if ['.','..'].include?(file) file = File.join(dir, file) File.unlink(file) if File.file?(file) end Dir.rmdir(dir) end end end diff --git a/spec/unit/reports/store_spec.rb b/spec/unit/reports/store_spec.rb index 73a7e353f..5b752c4b7 100755 --- a/spec/unit/reports/store_spec.rb +++ b/spec/unit/reports/store_spec.rb @@ -1,30 +1,48 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/reports' require 'time' +require 'pathname' +require 'tempfile' +require 'fileutils' processor = Puppet::Reports.report(:store) describe processor do describe "#process" do include PuppetSpec::Files before :each do - Puppet[:reportdir] = tmpdir('reports') << '/reports' + Puppet[:reportdir] = File.join(tmpdir('reports'), 'reports') @report = YAML.load_file(File.join(PuppetSpec::FIXTURE_DIR, 'yaml/report2.6.x.yaml')).extend processor end it "should create a report directory for the client if one doesn't exist" do @report.process File.should be_directory(File.join(Puppet[:reportdir], @report.host)) end it "should write the report to the file in YAML" do Time.stubs(:now).returns(Time.parse("2011-01-06 12:00:00 UTC")) @report.process File.read(File.join(Puppet[:reportdir], @report.host, "201101061200.yaml")).should == @report.to_yaml end + + it "should write to the report directory in the correct sequence" do + # By doing things in this sequence we should protect against race + # conditions + Time.stubs(:now).returns(Time.parse("2011-01-06 12:00:00 UTC")) + writeseq = sequence("write") + file = mock "file" + Tempfile.expects(:new).in_sequence(writeseq).returns(file) + file.expects(:chmod).in_sequence(writeseq).with(0640) + file.expects(:print).with(@report.to_yaml).in_sequence(writeseq) + file.expects(:close).in_sequence(writeseq) + file.stubs(:path).returns(File.join(Dir.tmpdir, "foo123")) + FileUtils.expects(:mv).in_sequence(writeseq).with(File.join(Dir.tmpdir, "foo123"), File.join(Puppet[:reportdir], @report.host, "201101061200.yaml")) + @report.process + end end end