diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb index a5fe9920a..f48e601fb 100755 --- a/lib/puppet/type/file/content.rb +++ b/lib/puppet/type/file/content.rb @@ -1,132 +1,132 @@ require 'puppet/util/checksums' module Puppet Puppet::Type.type(:file).newproperty(:content) do include Puppet::Util::Diff include Puppet::Util::Checksums desc "Specify the contents of a file as a string. Newlines, tabs, and spaces can be specified using the escaped syntax (e.g., \\n for a newline). The primary purpose of this parameter is to provide a kind of limited templating:: define resolve(nameserver1, nameserver2, domain, search) { $str = \"search $search domain $domain nameserver $nameserver1 nameserver $nameserver2 \" file { \"/etc/resolv.conf\": content => $str } } This attribute is especially useful when used with `PuppetTemplating templating`:trac:." # Store a checksum as the value, rather than the actual content. # Simplifies everything. munge do |value| if value == :absent value else @actual_content = value "{#{checksum_type}}" + send(self.checksum_type, value) end end def checksum_type if source = resource.parameter(:source) source.checksum =~ /^\{(\w+)\}.+/ return $1.to_sym elsif checksum = resource.parameter(:checksum) result = checksum.checktype if result =~ /^\{(\w+)\}.+/ return $1.to_sym else return result end else return :md5 end end # If content was specified, return that; else try to return the source content; # else, return nil. def actual_content if defined?(@actual_content) and @actual_content return @actual_content end if s = resource.parameter(:source) return s.content end return nil end def content self.should || (s = resource.parameter(:source) and s.content) end # Override this method to provide diffs if asked for. # Also, fix #872: when content is used, and replace is true, the file # should be insync when it exists def insync?(is) if resource.should_be_file? return false if is == :absent else return true end return true if ! @resource.replace? if self.should return super elsif source = resource.parameter(:source) fail "Got a remote source with no checksum" unless source.checksum result = (is == source.checksum) else # We've got no content specified, and no source from which to # get content. return true end if ! result and Puppet[:show_diff] string_file_diff(@resource[:path], actual_content) end return result end def retrieve return :absent unless stat = @resource.stat # Don't even try to manage the content on directories or links return nil if stat.ftype == "directory" begin - return "{#{checksum_type}}" + send(checksum_type.to_s + "_file", resource[:path]) + return "{#{checksum_type}}" + send(checksum_type.to_s + "_file", resource[:path]).to_s rescue => detail raise Puppet::Error, "Could not read %s: %s" % [@resource.title, detail] end end # Make sure we're also managing the checksum property. def should=(value) @resource.newattr(:checksum) unless @resource.parameter(:checksum) super end # Just write our content out to disk. def sync return_event = @resource.stat ? :file_changed : :file_created # We're safe not testing for the 'source' if there's no 'should' # because we wouldn't have gotten this far if there weren't at least # one valid value somewhere. @resource.write(actual_content, :content) return return_event end end end diff --git a/spec/unit/type/file/content.rb b/spec/unit/type/file/content.rb index fd225fa17..0529cd33f 100755 --- a/spec/unit/type/file/content.rb +++ b/spec/unit/type/file/content.rb @@ -1,253 +1,268 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } content = Puppet::Type.type(:file).attrclass(:content) describe content do before do # Wow that's a messy interface to the resource. @resource = stub 'resource', :[] => nil, :[]= => nil, :property => nil, :newattr => nil, :parameter => nil end it "should be a subclass of Property" do content.superclass.must == Puppet::Property end describe "when determining the checksum type" do it "should use the type specified in the source checksum if a source is set" do source = mock 'source' source.expects(:checksum).returns "{litemd5}eh" @resource.expects(:parameter).with(:source).returns source @content = content.new(:resource => @resource) @content.checksum_type.should == :litemd5 end it "should use the type specified by the checksum parameter if no source is set" do checksum = mock 'checksum' checksum.expects(:checktype).returns :litemd5 @resource.expects(:parameter).with(:source).returns nil @resource.expects(:parameter).with(:checksum).returns checksum @content = content.new(:resource => @resource) @content.checksum_type.should == :litemd5 end it "should only return the checksum type from the checksum parameter if the parameter returns a whole checksum" do checksum = mock 'checksum' checksum.expects(:checktype).returns "{md5}something" @resource.expects(:parameter).with(:source).returns nil @resource.expects(:parameter).with(:checksum).returns checksum @content = content.new(:resource => @resource) @content.checksum_type.should == :md5 end it "should use md5 if neither a source nor a checksum parameter is available" do @content = content.new(:resource => @resource) @content.checksum_type.should == :md5 end end describe "when determining the actual content to write" do it "should use the set content if available" do @content = content.new(:resource => @resource) @content.should = "ehness" @content.actual_content.should == "ehness" end it "should use the content from the source if the source is set" do source = mock 'source' source.expects(:content).returns "scont" @resource.expects(:parameter).with(:source).returns source @content = content.new(:resource => @resource) @content.actual_content.should == "scont" end it "should return nil if no source is available and no content is set" do @content = content.new(:resource => @resource) @content.actual_content.should be_nil end end describe "when setting the desired content" do it "should make the actual content available via an attribute" do @content = content.new(:resource => @resource) @content.stubs(:checksum_type).returns "md5" @content.should = "this is some content" @content.actual_content.should == "this is some content" end it "should store the checksum as the desired content" do @content = content.new(:resource => @resource) digest = Digest::MD5.hexdigest("this is some content") @content.stubs(:checksum_type).returns "md5" @content.should = "this is some content" @content.should.must == "{md5}#{digest}" end it "should not checksum 'absent'" do @content = content.new(:resource => @resource) @content.should = :absent @content.should.must == :absent end end describe "when retrieving the current content" do it "should return :absent if the file does not exist" do @content = content.new(:resource => @resource) @resource.expects(:stat).returns nil @content.retrieve.should == :absent end it "should not manage content on directories" do @content = content.new(:resource => @resource) stat = mock 'stat', :ftype => "directory" @resource.expects(:stat).returns stat @content.retrieve.should be_nil end + it "should always return the checksum as a string" do + @content = content.new(:resource => @resource) + @content.stubs(:checksum_type).returns "mtime" + + stat = mock 'stat', :ftype => "file" + @resource.expects(:stat).returns stat + + @resource.expects(:[]).with(:path).returns "/my/file" + + time = Time.now + @content.expects(:mtime_file).with("/my/file").returns time + + @content.retrieve.should == "{mtime}%s" % time + end + it "should return the checksum of the file if it exists and is a normal file" do @content = content.new(:resource => @resource) @content.stubs(:checksum_type).returns "md5" stat = mock 'stat', :ftype => "file" @resource.expects(:stat).returns stat @resource.expects(:[]).with(:path).returns "/my/file" @content.expects(:md5_file).with("/my/file").returns "mysum" @content.retrieve.should == "{md5}mysum" end end describe "when testing whether the content is in sync" do before do @resource.stubs(:[]).with(:ensure).returns :file @resource.stubs(:replace?).returns true @resource.stubs(:should_be_file?).returns true @content = content.new(:resource => @resource) @content.stubs(:checksum_type).returns "md5" end it "should return true if the resource shouldn't be a regular file" do @resource.expects(:should_be_file?).returns false @content.must be_insync("whatever") end it "should return false if the current content is :absent" do @content.should_not be_insync(:absent) end it "should return false if the file should be a file but is not present" do @resource.expects(:should_be_file?).returns true @content.should_not be_insync(:absent) end describe "and the file exists" do before do @resource.stubs(:stat).returns mock("stat") end it "should return false if the current contents are different from the desired content" do @content.should = "some content" @content.should_not be_insync("other content") end it "should return true if the sum for the current contents is the same as the sum for the desired content" do @content.should = "some content" @content.must be_insync("{md5}" + Digest::MD5.hexdigest("some content")) end describe "and the content is specified via a remote source" do before do @metadata = stub 'metadata' @source = stub 'source', :metadata => @metadata @resource.stubs(:parameter).with(:source).returns @source end it "should use checksums to compare remote content, rather than downloading the content" do @source.stubs(:checksum).returns "{md5}whatever" @content.insync?("{md5}eh") end it "should return false if the current content is different from the remote content" do @source.stubs(:checksum).returns "{md5}whatever" @content.should_not be_insync("some content") end it "should return true if the current content is the same as the remote content" do @source.stubs(:checksum).returns("{md5}something") @content.must be_insync("{md5}something") end end end describe "and :replace is false" do before do @resource.stubs(:replace?).returns false end it "should be insync if the file exists and the content is different" do @resource.stubs(:stat).returns mock('stat') @content.must be_insync("whatever") end it "should be insync if the file exists and the content is right" do @resource.stubs(:stat).returns mock('stat') @content.must be_insync("something") end it "should not be insync if the file does not exist" do @content.should_not be_insync(:absent) end end end describe "when changing the content" do before do @content = content.new(:resource => @resource) @content.should = "some content" @resource.stubs(:[]).with(:path).returns "/boo" @resource.stubs(:stat).returns "eh" end it "should use the file's :write method to write the content" do @resource.expects(:write).with("some content", :content) @content.sync end it "should return :file_changed if the file already existed" do @resource.expects(:stat).returns "something" @resource.stubs(:write) @content.sync.should == :file_changed end it "should return :file_created if the file did not exist" do @resource.expects(:stat).returns nil @resource.stubs(:write) @content.sync.should == :file_created end end end