diff --git a/lib/puppet/type/pfile/ensure.rb b/lib/puppet/type/pfile/ensure.rb index 0a6f73d95..3aa918f65 100755 --- a/lib/puppet/type/pfile/ensure.rb +++ b/lib/puppet/type/pfile/ensure.rb @@ -1,174 +1,179 @@ module Puppet Puppet.type(:file).ensurable do require 'etc' desc "Whether to create files that don't currently exist. Possible values are *absent*, *present* (will match any form of file existence, and if the file is missing will create an empty file), *file*, and *directory*. Specifying ``absent`` will delete the file, although currently this will not recursively delete directories. Anything other than those values will be considered to be a symlink. For instance, the following text creates a link:: # Useful on solaris file { \"/etc/inetd.conf\": ensure => \"/etc/inet/inetd.conf\" } You can make relative links:: # Useful on solaris file { \"/etc/inetd.conf\": ensure => \"inet/inetd.conf\" } If you need to make a relative link to a file named the same as one of the valid values, you must prefix it with ``./`` or something similar. You can also make recursive symlinks, which will create a directory structure that maps to the target directory, with directories corresponding to each directory and links corresponding to each file." # Most 'ensure' properties have a default, but with files we, um, don't. nodefault newvalue(:absent) do File.unlink(@resource[:path]) end aliasvalue(:false, :absent) newvalue(:file) do # Make sure we're not managing the content some other way if property = (@resource.property(:content) || @resource.property(:source)) property.sync else @resource.write(false) { |f| f.flush } mode = @resource.should(:mode) end return :file_created end #aliasvalue(:present, :file) newvalue(:present) do # Make a file if they want something, but this will match almost # anything. set_file end newvalue(:directory) do mode = @resource.should(:mode) parent = File.dirname(@resource[:path]) unless FileTest.exists? parent raise Puppet::Error, "Cannot create %s; parent directory %s does not exist" % [@resource[:path], parent] end @resource.write_if_writable(parent) do if mode Puppet::Util.withumask(000) do Dir.mkdir(@resource[:path],mode) end else Dir.mkdir(@resource[:path]) end end @resource.send(:property_fix) @resource.setchecksum return :directory_created end newvalue(:link) do if property = @resource.property(:target) property.retrieve return property.mklink else self.fail "Cannot create a symlink without a target" end end # Symlinks. newvalue(/./) do # This code never gets executed. We need the regex to support # specifying it, but the work is done in the 'symlink' code block. end munge do |value| value = super(value) return value if value.is_a? Symbol @resource[:target] = value return :link end def change_to_s(currentvalue, newvalue) if property = (@resource.property(:content) || @resource.property(:source)) and ! property.insync?(currentvalue) currentvalue = property.retrieve return property.change_to_s(property.retrieve, property.should) else super(currentvalue, newvalue) end end # Check that we can actually create anything def check basedir = File.dirname(@resource[:path]) if ! FileTest.exists?(basedir) raise Puppet::Error, "Can not create %s; parent directory does not exist" % @resource.title elsif ! FileTest.directory?(basedir) raise Puppet::Error, "Can not create %s; %s is not a directory" % [@resource.title, dirname] end end # We have to treat :present specially, because it works with any # type of file. def insync?(currentvalue) + if property = @resource.property(:source) and ! property.described? + warning "No specified sources exist" + return true + end + if self.should == :present if currentvalue.nil? or currentvalue == :absent return false else return true end else return super(currentvalue) end end def retrieve if stat = @resource.stat(false) return stat.ftype.intern else if self.should == :false return :false else return :absent end end end def sync @resource.remove_existing(self.should) if self.should == :absent return :file_removed end event = super return event end end end diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb index 1849d5a61..3dfb5cccd 100755 --- a/lib/puppet/type/pfile/source.rb +++ b/lib/puppet/type/pfile/source.rb @@ -1,297 +1,297 @@ module Puppet # Copy files from a local or remote source. This state *only* does any work # when the remote file is an actual file; in that case, this state copies # the file down. If the remote file is a dir or a link or whatever, then # this state, during retrieval, modifies the appropriate other states # so that things get taken care of appropriately. Puppet.type(:file).newproperty(:source) do include Puppet::Util::Diff attr_accessor :source, :local desc "Copy a file over the current file. Uses ``checksum`` to determine when a file should be copied. Valid values are either fully qualified paths to files, or URIs. Currently supported URI types are *puppet* and *file*. This is one of the primary mechanisms for getting content into applications that Puppet does not directly support and is very useful for those configuration files that don't change much across sytems. For instance:: class sendmail { file { \"/etc/mail/sendmail.cf\": source => \"puppet://server/module/sendmail.cf\" } } You can also leave out the server name, in which case ``puppetd`` will fill in the name of its configuration server and ``puppet`` will use the local filesystem. This makes it easy to use the same configuration in both local and centralized forms. Currently, only the ``puppet`` scheme is supported for source URL's. Puppet will connect to the file server running on ``server`` to retrieve the contents of the file. If the ``server`` part is empty, the behavior of the command-line interpreter (``puppet``) and the client demon (``puppetd``) differs slightly: ``puppet`` will look such a file up on the module path on the local host, whereas ``puppetd`` will connect to the puppet server that it received the manifest from. See the `FileServingConfiguration fileserver configuration documentation`:trac: for information on how to configure and use file services within Puppet. If you specify multiple file sources for a file, then the first source that exists will be used. This allows you to specify what amount to search paths for files:: file { \"/path/to/my/file\": source => [ \"/nfs/files/file.$host\", \"/nfs/files/file.$operatingsystem\", \"/nfs/files/file\" ] } This will use the first found file as the source. You cannot currently copy links using this mechanism; set ``links`` to ``follow`` if any remote sources are links. " uncheckable validate do |source| unless @resource.uri2obj(source) raise Puppet::Error, "Invalid source %s" % source end end munge do |source| # if source.is_a? Symbol # return source # end # Remove any trailing slashes source.sub(/\/$/, '') end def change_to_s(currentvalue, newvalue) # newvalue = "{md5}" + @stats[:checksum] if @resource.property(:ensure).retrieve == :absent return "creating from source %s with contents %s" % [@source, @stats[:checksum]] else return "replacing from source %s with contents %s" % [@source, @stats[:checksum]] end end def checksum if defined?(@stats) @stats[:checksum] else nil end end # Ask the file server to describe our file. def describe(source) sourceobj, path = @resource.uri2obj(source) server = sourceobj.server begin desc = server.describe(path, @resource[:links]) rescue Puppet::Network::XMLRPCClientError => detail self.err "Could not describe %s: %s" % [path, detail] return nil end args = {} pinparams.zip( desc.split("\t") ).each { |param, value| if value =~ /^[0-9]+$/ value = value.to_i end unless value.nil? args[param] = value end } # we can't manage ownership as root, so don't even try unless Puppet::Util::SUIDManager.uid == 0 args.delete(:owner) end if args.empty? or (args[:type] == "link" and @resource[:links] == :ignore) return nil else return args end end # Have we successfully described the remote source? def described? ! @stats.nil? and ! @stats[:type].nil? #and @is != :notdescribed end # Use the info we get from describe() to check if we're in sync. def insync?(currentvalue) unless described? - info "No specified sources exist" + warning "No specified sources exist" return true end - + if currentvalue == :nocopy return true end # the only thing this actual state can do is copy files around. Therefore, # only pay attention if the remote is a file. unless @stats[:type] == "file" return true end #FIXARB: Inefficient? Needed to call retrieve on parent's ensure and checksum parentensure = @resource.property(:ensure).retrieve if parentensure != :absent and ! @resource.replace? return true end # Now, we just check to see if the checksums are the same parentchecksum = @resource.property(:checksum).retrieve result = (!parentchecksum.nil? and (parentchecksum == @stats[:checksum])) # Diff the contents if they ask it. This is quite annoying -- we need to do this in # 'insync?' because they might be in noop mode, but we don't want to do the file # retrieval twice, so we cache the value annoyingly. if ! result and Puppet[:show_diff] and File.exists?(@resource[:path]) and ! @stats[:_diffed] @stats[:_remote_content] = get_remote_content string_file_diff(@resource[:path], @stats[:_remote_content]) @stats[:_diffed] = true end return result end def pinparams Puppet::Network::Handler.handler(:fileserver).params end # This basically calls describe() on our file, and then sets all # of the local states appropriately. If the remote file is a normal # file then we set it to copy; if it's a directory, then we just mark # that the local directory should be created. def retrieve(remote = true) sum = nil @source = nil # This is set to false by the File#retrieve function on the second # retrieve, so that we do not do two describes. if remote # Find the first source that exists. @shouldorig contains # the sources as specified by the user. @should.each { |source| if @stats = self.describe(source) @source = source break end } end if @stats.nil? or @stats[:type].nil? return nil # :notdescribed end case @stats[:type] when "directory", "file": unless @resource.deleting? @resource[:ensure] = @stats[:type] end else self.info @stats.inspect self.err "Cannot use files of type %s as sources" % @stats[:type] return :nocopy end # Take each of the stats and set them as states on the local file # if a value has not already been provided. @stats.each { |stat, value| next if stat == :checksum next if stat == :type # was the stat already specified, or should the value # be inherited from the source? unless @resource.argument?(stat) @resource[stat] = value end } return @stats[:checksum] end def should @should end # Make sure we're also checking the checksum def should=(value) super checks = (pinparams + [:ensure]) checks.delete(:checksum) @resource[:check] = checks unless @resource.property(:checksum) @resource[:checksum] = :md5 end end def sync contents = @stats[:_remote_content] || get_remote_content() exists = File.exists?(@resource[:path]) @resource.write(:source) { |f| f.print contents } if exists return :file_changed else return :file_created end end private def get_remote_content unless @stats[:type] == "file" #if @stats[:type] == "directory" #[@resource.name, @should.inspect] #end raise Puppet::DevError, "Got told to copy non-file %s" % @resource[:path] end sourceobj, path = @resource.uri2obj(@source) begin contents = sourceobj.server.retrieve(path, @resource[:links]) rescue Puppet::Network::XMLRPCClientError => detail self.err "Could not retrieve %s: %s" % [path, detail] return nil end # FIXME It's stupid that this isn't taken care of in the # protocol. unless sourceobj.server.local contents = CGI.unescape(contents) end if contents == "" self.notice "Could not retrieve contents for %s" % @source end return contents end end end