diff options
author | Luke Kanies <luke@madstop.com> | 2008-09-23 23:50:43 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-09-23 23:50:43 -0500 |
commit | bb23861e334e617b544c11bc75a35c40b36185a2 (patch) | |
tree | 18da91858e4fded78a56d673fc69014fdf266676 /lib | |
parent | e31df2f7f5e98c524b68cd724cfaa3e308e7b9a1 (diff) | |
parent | ac5db5ec115455e54090542870847820357739a2 (diff) | |
download | puppet-bb23861e334e617b544c11bc75a35c40b36185a2.tar.gz puppet-bb23861e334e617b544c11bc75a35c40b36185a2.tar.xz puppet-bb23861e334e617b544c11bc75a35c40b36185a2.zip |
Merge branch 'feature/master/1481'
This merges in the new fileserving code -- we're now using
REST to do fileserving, rather than xmlrpc.
Conflicts:
lib/puppet/parameter.rb
lib/puppet/type/file.rb
spec/unit/type/file.rb
Diffstat (limited to 'lib')
25 files changed, 440 insertions, 540 deletions
diff --git a/lib/puppet/file_serving/file_base.rb b/lib/puppet/file_serving/base.rb index e87d683aa..c59a54786 100644 --- a/lib/puppet/file_serving/file_base.rb +++ b/lib/puppet/file_serving/base.rb @@ -6,8 +6,10 @@ require 'puppet/file_serving' # The base class for Content and Metadata; provides common # functionality like the behaviour around links. -class Puppet::FileServing::FileBase - attr_accessor :key +class Puppet::FileServing::Base + # This is for external consumers to store the source that was used + # to retrieve the metadata. + attr_accessor :source # Does our file exist? def exist? @@ -21,17 +23,15 @@ class Puppet::FileServing::FileBase # Return the full path to our file. Fails if there's no path set. def full_path - raise(ArgumentError, "You must set a path to get a file's path") unless self.path - - if relative_path.nil? or relative_path == "" + if relative_path.nil? or relative_path == "" or relative_path == "." path else File.join(path, relative_path) end end - def initialize(key, options = {}) - @key = key + def initialize(path, options = {}) + self.path = path @links = :manage options.each do |param, value| diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb index 9c38aaa19..bceecc30c 100644 --- a/lib/puppet/file_serving/configuration.rb +++ b/lib/puppet/file_serving/configuration.rb @@ -98,13 +98,14 @@ class Puppet::FileServing::Configuration # Reparse the configuration if necessary. readconfig - raise(ArgumentError, "Cannot find file: Invalid path '%s'" % uri) unless uri =~ %r{/([-\w]+)/?} + raise(ArgumentError, "Cannot find file: Invalid path '%s'" % uri) unless uri =~ %r{^([-\w]+)(/|$)} # the dir is based on one of the mounts # so first retrieve the mount path mount = path = nil + # Strip off the mount name. - mount_name, path = uri.sub(%r{^/}, '').split(File::Separator, 2) + mount_name, path = uri.split(File::Separator, 2) return nil unless mount = @mounts[mount_name] diff --git a/lib/puppet/file_serving/content.rb b/lib/puppet/file_serving/content.rb index 9398513e7..c1ecff749 100644 --- a/lib/puppet/file_serving/content.rb +++ b/lib/puppet/file_serving/content.rb @@ -4,31 +4,46 @@ require 'puppet/indirector' require 'puppet/file_serving' -require 'puppet/file_serving/file_base' +require 'puppet/file_serving/base' require 'puppet/file_serving/indirection_hooks' # A class that handles retrieving file contents. # It only reads the file when its content is specifically # asked for. -class Puppet::FileServing::Content < Puppet::FileServing::FileBase +class Puppet::FileServing::Content < Puppet::FileServing::Base extend Puppet::Indirector indirects :file_content, :extend => Puppet::FileServing::IndirectionHooks - attr_reader :path + attr_writer :content + + def self.supported_formats + [:raw] + end + + def self.from_raw(content) + instance = new("/this/is/a/fake/path") + instance.content = content + instance + end + + # Collect our data. + def collect + return if stat.ftype == "directory" + content + end # Read the content of our file in. def content - # This stat can raise an exception, too. - raise(ArgumentError, "Cannot read the contents of links unless following links") if stat().ftype == "symlink" + unless defined?(@content) and @content + # This stat can raise an exception, too. + raise(ArgumentError, "Cannot read the contents of links unless following links") if stat().ftype == "symlink" - ::File.read(full_path()) + @content = ::File.read(full_path()) + end + @content end - # Just return the file contents as the yaml. This allows us to - # avoid escaping or any such thing. LAK:NOTE Not really sure how - # this will behave if the file contains yaml... I think the far - # side needs to understand that it's a plain string. - def to_yaml + def to_raw content end end diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb index fe54350b1..a90734a2b 100644 --- a/lib/puppet/file_serving/fileset.rb +++ b/lib/puppet/file_serving/fileset.rb @@ -20,7 +20,7 @@ class Puppet::FileServing::Fileset # Now strip off the leading path, so each file becomes relative, and remove # any slashes that might end up at the beginning of the path. - result = files.collect { |file| file.sub(%r{^#{@path}/*}, '') } + result = files.collect { |file| file.sub(@path, '').sub(%r{^/},'') } # And add the path itself. result.unshift(".") @@ -30,6 +30,8 @@ class Puppet::FileServing::Fileset # Should we ignore this path? def ignore?(path) + return false if @ignore == [nil] + # 'detect' normally returns the found result, whereas we just want true/false. ! @ignore.detect { |pattern| File.fnmatch?(pattern, path) }.nil? end diff --git a/lib/puppet/file_serving/indirection_hooks.rb b/lib/puppet/file_serving/indirection_hooks.rb index 66ed169dc..15564cf3d 100644 --- a/lib/puppet/file_serving/indirection_hooks.rb +++ b/lib/puppet/file_serving/indirection_hooks.rb @@ -9,36 +9,37 @@ require 'puppet/file_serving' # in file-serving indirections. This is necessary because # the terminus varies based on the URI asked for. module Puppet::FileServing::IndirectionHooks - PROTOCOL_MAP = {"puppet" => :rest, "file" => :file, "puppetmounts" => :file_server} + PROTOCOL_MAP = {"puppet" => :rest, "file" => :file} # Pick an appropriate terminus based on the protocol. def select_terminus(request) - full_uri = request.key - # Short-circuit to :file if it's a fully-qualified path. - return PROTOCOL_MAP["file"] if full_uri =~ /^#{::File::SEPARATOR}/ - begin - uri = URI.parse(URI.escape(full_uri)) - rescue => detail - raise ArgumentError, "Could not understand URI %s: %s" % [full_uri, detail.to_s] - end + # We rely on the request's parsing of the URI. - terminus = PROTOCOL_MAP[uri.scheme] || raise(ArgumentError, "URI protocol '%s' is not supported for file serving" % uri.scheme) + # Short-circuit to :file if it's a fully-qualified path or specifies a 'file' protocol. + return PROTOCOL_MAP["file"] if request.key =~ /^#{::File::SEPARATOR}/ + return PROTOCOL_MAP["file"] if request.protocol == "file" - # This provides a convenient mechanism for people to write configurations work - # well in both a networked and local setting. - if uri.host.nil? and uri.scheme == "puppet" and Puppet.settings[:name] == "puppet" - terminus = :file_server + # We're heading over the wire the protocol is 'puppet' and we've got a server name or we're not named 'puppet' + if request.protocol == "puppet" and (request.server or Puppet.settings[:name] != "puppet") + return PROTOCOL_MAP["puppet"] + end + + if request.protocol and PROTOCOL_MAP[request.protocol].nil? + raise(ArgumentError, "URI protocol '%s' is not currently supported for file serving" % request.protocol) end + # If we're still here, we're using the file_server or modules. + # This is the backward-compatible module terminus. - if terminus == :file_server and uri.path =~ %r{^/([^/]+)\b} - modname = $1 - if modname == "modules" - terminus = :modules - elsif terminus(:modules).find_module(modname, request.options[:node]) - Puppet.warning "DEPRECATION NOTICE: Found file '%s' in module without using the 'modules' mount; please prefix path with '/modules'" % uri.path - terminus = :modules - end + modname = request.key.split("/")[0] + + if modname == "modules" + terminus = :modules + elsif terminus(:modules).find_module(modname, request.options[:node]) + Puppet.warning "DEPRECATION NOTICE: Found file '%s' in module without using the 'modules' mount; please prefix path with 'modules/'" % request.key + terminus = :modules + else + terminus = :file_server end return terminus diff --git a/lib/puppet/file_serving/metadata.rb b/lib/puppet/file_serving/metadata.rb index beecaef48..1cc3fa355 100644 --- a/lib/puppet/file_serving/metadata.rb +++ b/lib/puppet/file_serving/metadata.rb @@ -5,12 +5,12 @@ require 'puppet' require 'puppet/indirector' require 'puppet/file_serving' -require 'puppet/file_serving/file_base' +require 'puppet/file_serving/base' require 'puppet/util/checksums' require 'puppet/file_serving/indirection_hooks' # A class that handles retrieving file metadata. -class Puppet::FileServing::Metadata < Puppet::FileServing::FileBase +class Puppet::FileServing::Metadata < Puppet::FileServing::Base include Puppet::Util::Checksums @@ -47,7 +47,7 @@ class Puppet::FileServing::Metadata < Puppet::FileServing::FileBase # Retrieve the attributes for this file, relative to a base directory. # Note that File.stat raises Errno::ENOENT if the file is absent and this # method does not catch that exception. - def collect_attributes + def collect real_path = full_path() stat = stat() @owner = stat.uid diff --git a/lib/puppet/file_serving/terminus_helper.rb b/lib/puppet/file_serving/terminus_helper.rb index e5da0e29f..b51e27297 100644 --- a/lib/puppet/file_serving/terminus_helper.rb +++ b/lib/puppet/file_serving/terminus_helper.rb @@ -9,10 +9,20 @@ require 'puppet/file_serving/fileset' module Puppet::FileServing::TerminusHelper # Create model instances for all files in a fileset. def path2instances(request, path) - args = [:links, :ignore, :recurse].inject({}) { |hash, param| hash[param] = request.options[param] if request.options[param]; hash } + args = [:links, :ignore, :recurse].inject({}) do |hash, param| + if request.options.include?(param) # use 'include?' so the values can be false + hash[param] = request.options[param] + elsif request.options.include?(param.to_s) + hash[param] = request.options[param.to_s] + end + hash[param] = true if hash[param] == "true" + hash[param] = false if hash[param] == "false" + hash + end Puppet::FileServing::Fileset.new(path, args).files.collect do |file| - inst = model.new(File.join(request.key, file), :path => path, :relative_path => file) + inst = model.new(path, :relative_path => file) inst.links = request.options[:links] if request.options[:links] + inst.collect inst end end diff --git a/lib/puppet/indirector/direct_file_server.rb b/lib/puppet/indirector/direct_file_server.rb index b3b4886f3..bcda92366 100644 --- a/lib/puppet/indirector/direct_file_server.rb +++ b/lib/puppet/indirector/direct_file_server.rb @@ -12,16 +12,14 @@ class Puppet::Indirector::DirectFileServer < Puppet::Indirector::Terminus include Puppet::FileServing::TerminusHelper def find(request) - uri = key2uri(request.key) - return nil unless FileTest.exists?(uri.path) - instance = model.new(request.key, :path => uri.path) + return nil unless FileTest.exists?(request.key) + instance = model.new(request.key) instance.links = request.options[:links] if request.options[:links] return instance end def search(request) - uri = key2uri(request.key) - return nil unless FileTest.exists?(uri.path) - path2instances(request, uri.path) + return nil unless FileTest.exists?(request.key) + path2instances(request, request.key) end end diff --git a/lib/puppet/indirector/file_metadata/file.rb b/lib/puppet/indirector/file_metadata/file.rb index c46015c38..bb586489d 100644 --- a/lib/puppet/indirector/file_metadata/file.rb +++ b/lib/puppet/indirector/file_metadata/file.rb @@ -11,7 +11,7 @@ class Puppet::Indirector::FileMetadata::File < Puppet::Indirector::DirectFileSer def find(request) return unless data = super - data.collect_attributes + data.collect return data end @@ -19,7 +19,7 @@ class Puppet::Indirector::FileMetadata::File < Puppet::Indirector::DirectFileSer def search(request) return unless result = super - result.each { |instance| instance.collect_attributes } + result.each { |instance| instance.collect } return result end diff --git a/lib/puppet/indirector/file_metadata/modules.rb b/lib/puppet/indirector/file_metadata/modules.rb index 5ed7a8a45..4598c2175 100644 --- a/lib/puppet/indirector/file_metadata/modules.rb +++ b/lib/puppet/indirector/file_metadata/modules.rb @@ -11,7 +11,7 @@ class Puppet::Indirector::FileMetadata::Modules < Puppet::Indirector::ModuleFile def find(*args) return unless instance = super - instance.collect_attributes + instance.collect instance end end diff --git a/lib/puppet/indirector/file_server.rb b/lib/puppet/indirector/file_server.rb index b0df7ff5d..46a590f9c 100644 --- a/lib/puppet/indirector/file_server.rb +++ b/lib/puppet/indirector/file_server.rb @@ -25,8 +25,9 @@ class Puppet::Indirector::FileServer < Puppet::Indirector::Terminus # Find our key using the fileserver. def find(request) return nil unless path = find_path(request) - result = model.new(request.key, :path => path) + result = model.new(path) result.links = request.options[:links] if request.options[:links] + result.collect return result end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index a9fff75b8..04c3aed23 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -262,7 +262,11 @@ class Puppet::Indirector::Indirection return unless terminus.respond_to?(:authorized?) unless terminus.authorized?(request) - raise ArgumentError, "Not authorized to call %s on %s with %s" % [request.method, request.key, request.options.inspect] + msg = "Not authorized to call %s on %s" % [request.method, request.key] + unless request.options.empty? + msg += " with %s" % request.options.inspect + end + raise ArgumentError, msg end end @@ -270,7 +274,9 @@ class Puppet::Indirector::Indirection def prepare(request) # Pick our terminus. if respond_to?(:select_terminus) - terminus_name = select_terminus(request) + unless terminus_name = select_terminus(request) + raise ArgumentError, "Could not determine appropriate terminus for %s" % request + end else terminus_name = terminus_class end diff --git a/lib/puppet/indirector/module_files.rb b/lib/puppet/indirector/module_files.rb index cf5c29cab..7c5cf278f 100644 --- a/lib/puppet/indirector/module_files.rb +++ b/lib/puppet/indirector/module_files.rb @@ -21,7 +21,7 @@ class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus # Make sure our file path starts with /modules, so that we authorize # against the 'modules' mount. - path = uri.path =~ /^\/modules/ ? uri.path : "/modules" + uri.path + path = uri.path =~ /^modules\// ? uri.path : "modules/" + uri.path configuration.authorized?(path, :node => request.node, :ipaddress => request.ip) end @@ -30,7 +30,7 @@ class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus def find(request) return nil unless path = find_path(request) - result = model.new(request.key, :path => path) + result = model.new(path) result.links = request.options[:links] if request.options[:links] return result end @@ -66,9 +66,8 @@ class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus def find_path(request) uri = key2uri(request.key) - # Strip off /modules if it's there -- that's how requests get routed to this terminus. - # Also, strip off the leading slash if present. - module_name, relative_path = uri.path.sub(/^\/modules\b/, '').sub(%r{^/}, '').split(File::Separator, 2) + # Strip off modules/ if it's there -- that's how requests get routed to this terminus. + module_name, relative_path = uri.path.sub(/^modules\//, '').sub(%r{^/}, '').split(File::Separator, 2) # And use the environment to look up the module. return nil unless mod = find_module(module_name, request.node) diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb index 98fa38885..49cc01aab 100644 --- a/lib/puppet/indirector/request.rb +++ b/lib/puppet/indirector/request.rb @@ -1,10 +1,13 @@ require 'puppet/indirector' -# Provide any attributes or functionality needed for indirected -# instances. +# This class encapsulates all of the information you need to make an +# Indirection call, and as a a result also handles REST calls. It's somewhat +# analogous to an HTTP Request object, except tuned for our Indirector. class Puppet::Indirector::Request attr_accessor :indirection_name, :key, :method, :options, :instance, :node, :ip, :authenticated + attr_accessor :server, :port, :uri, :protocol + # Is this an authenticated request? def authenticated? # Double negative, so we just get true or false @@ -28,7 +31,15 @@ class Puppet::Indirector::Request end if key.is_a?(String) or key.is_a?(Symbol) - @key = key + # If the request key is a URI, then we need to treat it specially, + # because it rewrites the key. We could otherwise strip server/port/etc + # info out in the REST class, but it seemed bad design for the REST + # class to rewrite the key. + if key.to_s =~ /^\w+:\/\// # it's a URI + set_uri_key(key) + else + @key = key + end else @instance = key @key = @instance.name @@ -39,4 +50,40 @@ class Puppet::Indirector::Request def indirection Puppet::Indirector::Indirection.instance(@indirection_name) end + + # Are we trying to interact with multiple resources, or just one? + def plural? + method == :search + end + + private + + # Parse the key as a URI, setting attributes appropriately. + def set_uri_key(key) + @uri = key + begin + uri = URI.parse(URI.escape(key)) + rescue => detail + raise ArgumentError, "Could not understand URL %s: %s" % [source, detail.to_s] + end + + # Just short-circuit these to full paths + if uri.scheme == "file" + @key = uri.path + return + end + + @server = uri.host if uri.host + + # If the URI class can look up the scheme, it will provide a port, + # otherwise it will default to '0'. + if uri.port.to_i == 0 and uri.scheme == "puppet" + @port = Puppet.settings[:masterport].to_i + else + @port = uri.port.to_i + end + + @protocol = uri.scheme + @key = uri.path.sub(/^\//, '') + end end diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index 4389dfb7e..5ac25f02d 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -1,8 +1,33 @@ require 'net/http' require 'uri' +require 'puppet/network/http_pool' + # Access objects via REST class Puppet::Indirector::REST < Puppet::Indirector::Terminus + + class << self + attr_reader :server_setting, :port_setting + end + + # Specify the setting that we should use to get the server name. + def self.use_server_setting(setting) + @server_setting = setting + end + + def self.server + return Puppet.settings[server_setting || :server] + end + + # Specify the setting that we should use to get the port. + def self.use_port_setting(setting) + @port_setting = setting + end + + def self.port + return Puppet.settings[port_setting || :masterport].to_i + end + # Figure out the content type, turn that into a format, and use the format # to extract the body of the response. def deserialize(response, multiple = false) @@ -33,20 +58,7 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus end def network(request) - if request.key =~ /^\w+:\/\// # it looks like a URI - begin - uri = URI.parse(URI.escape(request.key)) - rescue => detail - raise ArgumentError, "Could not understand URL %s: %s" % [source, detail.to_s] - end - server = uri.host || Puppet[:server] - port = uri.port.to_i == 0 ? Puppet[:masterport].to_i : uri.port.to_i - else - server = Puppet[:server] - port = Puppet[:masterport].to_i - end - - Puppet::Network::HttpPool.http_instance(server, port) + Puppet::Network::HttpPool.http_instance(request.server || self.class.server, request.port || self.class.port) end def find(request) @@ -77,7 +89,7 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus private - # Create the qurey string, if options are present. + # Create the query string, if options are present. def query_string(request) return "" unless request.options and ! request.options.empty? "?" + request.options.collect { |key, value| "%s=%s" % [key, value] }.join("&") diff --git a/lib/puppet/network/format.rb b/lib/puppet/network/format.rb index 5f259fa49..21aead7cc 100644 --- a/lib/puppet/network/format.rb +++ b/lib/puppet/network/format.rb @@ -5,7 +5,7 @@ require 'puppet/provider/confiner' class Puppet::Network::Format include Puppet::Provider::Confiner - attr_reader :name, :mime + attr_reader :name, :mime, :weight def initialize(name, options = {}, &block) @name = name.to_s.downcase.intern @@ -17,6 +17,13 @@ class Puppet::Network::Format self.mime = "text/%s" % name end + if weight = options[:weight] + @weight = weight + options.delete(:weight) + else + @weight = 5 + end + unless options.empty? raise ArgumentError, "Unsupported option(s) %s" % options.keys end @@ -55,7 +62,8 @@ class Puppet::Network::Format end def supported?(klass) - klass.respond_to?(intern_method) and + suitable? and + klass.respond_to?(intern_method) and klass.respond_to?(intern_multiple_method) and klass.respond_to?(render_multiple_method) and klass.instance_methods.include?(render_method) diff --git a/lib/puppet/network/format_handler.rb b/lib/puppet/network/format_handler.rb index 4c9f4e59e..f3c3380e1 100644 --- a/lib/puppet/network/format_handler.rb +++ b/lib/puppet/network/format_handler.rb @@ -61,7 +61,10 @@ module Puppet::Network::FormatHandler end def supported_formats - format_handler.formats.collect { |f| format_handler.format(f) }.find_all { |f| f.supported?(self) }.collect { |f| f.name } + format_handler.formats.collect { |f| format_handler.format(f) }.find_all { |f| f.supported?(self) }.collect { |f| f.name }.sort do |a, b| + # It's an inverse sort -- higher weight formats go first. + format_handler.format(b).weight <=> format_handler.format(a).weight + end end end diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb index 8e4c59fb3..85e8ce6f8 100644 --- a/lib/puppet/network/formats.rb +++ b/lib/puppet/network/formats.rb @@ -42,7 +42,7 @@ Puppet::Network::FormatHandler.create(:marshal, :mime => "text/marshal") do Marshal.dump(instance) end - # Yaml monkey-patches Array, so this works. + # Marshal monkey-patches Array, so this works. def render_multiple(instances) Marshal.dump(instances) end @@ -54,3 +54,24 @@ Puppet::Network::FormatHandler.create(:marshal, :mime => "text/marshal") do end Puppet::Network::FormatHandler.create(:s, :mime => "text/plain") + +# A very low-weight format so it'll never get chosen automatically. +Puppet::Network::FormatHandler.create(:raw, :mime => "application/x-raw", :weight => 1) do + def intern_multiple(klass, text) + raise NotImplementedError + end + + def render_multiple(instances) + raise NotImplementedError + end + + # LAK:NOTE The format system isn't currently flexible enough to handle + # what I need to support raw formats just for individual instances (rather + # than both individual and collections), but we don't yet have enough data + # to make a "correct" design. + # So, we hack it so it works for singular but fail if someone tries it + # on plurals. + def supported?(klass) + true + end +end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index 183979429..14319ef96 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -75,7 +75,7 @@ class Puppet::Network::Handler return "" unless metadata.exist? begin - metadata.collect_attributes + metadata.collect rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 6f5117b16..7c7abccf5 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -12,7 +12,18 @@ module Puppet::Network::HTTP::Handler # Which format to use when serializing our response. Just picks # the first value in the accept header, at this point. def format_to_use(request) - accept_header(request).split(/,\s*/)[0] + unless header = accept_header(request) + raise ArgumentError, "An Accept header must be provided to pick the right format" + end + + format = nil + header.split(/,\s*/).each do |name| + next unless format = Puppet::Network::FormatHandler.format(name) + next unless format.suitable? + return name + end + + raise "No specified acceptable formats (%s) are functional on this machine" % header end def initialize_for_puppet(args = {}) diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb index d265dde86..45d21ea62 100644 --- a/lib/puppet/network/http/mongrel/rest.rb +++ b/lib/puppet/network/http/mongrel/rest.rb @@ -35,7 +35,7 @@ class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler # return the key included in the request path def request_key(request) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = request.params[Mongrel::Const::REQUEST_PATH].split('/')[2] + x = request.params[Mongrel::Const::REQUEST_PATH].split('/', 3)[2] end # return the request body diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb index 13f795fb2..f06914365 100644 --- a/lib/puppet/network/http/webrick/rest.rb +++ b/lib/puppet/network/http/webrick/rest.rb @@ -36,7 +36,7 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet def request_key(request) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = request.path.split('/')[2] + x = request.path.split('/', 3)[2] end def body(request) diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 11e8c8248..eb663968e 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -793,7 +793,6 @@ class Type obj.remove end @parameters.clear - self.class.delete(self) @parent = nil @@ -1519,6 +1518,7 @@ class Type # to an object... tname, name = value reference = Puppet::ResourceReference.new(tname, name) + reference.catalog = resource.catalog # Either of the two retrieval attempts could have returned # nil. @@ -1932,7 +1932,10 @@ class Type # Figure out of there are any objects we can automatically add as # dependencies. - def autorequire + def autorequire(rel_catalog = nil) + rel_catalog ||= catalog + raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog + reqs = [] self.class.eachautorequire { |type, block| # Ignore any types we can't find, although that would be a bit odd. @@ -1954,11 +1957,11 @@ class Type next end end - + reqs << Puppet::Relationship.new(dep, self) } } - + return reqs end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index c6ab2570c..370ce1b4f 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -155,8 +155,6 @@ module Puppet engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``. Matches that would descend into the directory structure are ignored, e.g., ``*/*``." - - defaultto false validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false @@ -277,11 +275,6 @@ module Puppet @depthfirst = false - - def argument?(arg) - @arghash.include?(arg) - end - # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) @@ -329,7 +322,23 @@ module Puppet # Create any children via recursion or whatever. def eval_generate - recurse() + return nil unless self.recurse? + + raise(Puppet::DevError, "Cannot generate resources for recursion without a catalog") unless catalog + + recurse.reject do |resource| + catalog.resource(:file, resource[:path]) + end.each do |child| + catalog.add_resource child + catalog.relationship_graph.add_edge self, child + end + end + + def flush + # We want to make sure we retrieve metadata anew on each transaction. + @parameters.each do |name, param| + param.flush if param.respond_to?(:flush) + end end # Deal with backups. @@ -455,194 +464,21 @@ module Puppet @title.sub!(/\/$/, "") unless @title == "/" - # Clean out as many references to any file paths as possible. - # This was the source of many, many bugs. - @arghash = tmphash - @arghash.delete(self.class.namevar) - - [:source, :parent].each do |param| - if @arghash.include?(param) - @arghash.delete(param) - end - end - @stat = nil end - - # Build a recursive map of a link source - def linkrecurse(recurse) - target = @parameters[:target].should - - method = :lstat - if self[:links] == :follow - method = :stat - end - - targetstat = nil - unless FileTest.exist?(target) - return - end - # Now stat our target - targetstat = File.send(method, target) - unless targetstat.ftype == "directory" - return - end - - # Now that we know our corresponding target is a directory, - # change our type - self[:ensure] = :directory - - unless FileTest.readable? target - self.notice "Cannot manage %s: permission denied" % self.name - return - end - - children = Dir.entries(target).reject { |d| d =~ /^\.+$/ } - - # Get rid of ignored children - if @parameters.include?(:ignore) - children = handleignore(children) - end - - added = [] - children.each do |file| - Dir.chdir(target) do - longname = File.join(target, file) - - # Files know to create directories when recursion - # is enabled and we're making links - args = { - :recurse => recurse, - :ensure => longname - } - - if child = self.newchild(file, true, args) - added << child - end - end - end - - added - end - - # Build up a recursive map of what's around right now - def localrecurse(recurse) - unless FileTest.exist?(self[:path]) and self.stat.directory? - #self.info "%s is not a directory; not recursing" % - # self[:path] - return - end - - unless FileTest.readable? self[:path] - self.notice "Cannot manage %s: permission denied" % self.name - return - end - - children = Dir.entries(self[:path]) - - #Get rid of ignored children - if @parameters.include?(:ignore) - children = handleignore(children) - end - - added = [] - children.each { |file| - file = File.basename(file) - next if file =~ /^\.\.?$/ # skip . and .. - options = {:recurse => recurse} - - if child = self.newchild(file, true, options) - added << child - end - } - - added - end # Create a new file or directory object as a child to the current # object. - def newchild(path, local, hash = {}) - raise(Puppet::DevError, "File recursion cannot happen without a catalog") unless catalog - - # make local copy of arguments - args = symbolize_options(@arghash) - - # There's probably a better way to do this, but we don't want - # to pass this info on. - if v = args[:ensure] - v = symbolize(v) - args.delete(:ensure) - end - - if path =~ %r{^#{File::SEPARATOR}} - self.devfail( - "Must pass relative paths to PFile#newchild()" - ) - else - path = File.join(self[:path], path) - end - - args[:path] = path - - unless hash.include?(:recurse) - if args.include?(:recurse) - if args[:recurse].is_a?(Integer) - args[:recurse] -= 1 # reduce the level of recursion - end - end - - end - - hash.each { |key,value| - args[key] = value - } - - child = nil - - # The child might already exist because 'localrecurse' runs - # before 'sourcerecurse'. I could push the override stuff into - # a separate method or something, but the work is the same other - # than this last bit, so it doesn't really make sense. - if child = catalog.resource(:file, path) - unless child.parent.object_id == self.object_id - self.debug "Not managing more explicit file %s" % - path - return nil - end + def newchild(path) + full_path = File.join(self[:path], path) - # This is only necessary for sourcerecurse, because we might have - # created the object with different 'should' values than are - # set remotely. - unless local - args.each { |var,value| - next if var == :path - next if var == :name - - # behave idempotently - unless child.should(var) == value - child[var] = value - end - } - end - return nil - else # create it anew - #notice "Creating new file with args %s" % args.inspect - args[:parent] = self - begin - # This method is used by subclasses of :file, so use the class name rather than hard-coding - # :file. - return nil unless child = catalog.create_implicit_resource(self.class.name, args) - rescue => detail - self.notice "Cannot manage: %s" % [detail] - return nil - end + # the right-side hash wins in the merge. + options = to_hash.merge(:path => full_path, :implicit => true).reject { |param, value| value.nil? } + [:parent, :recurse, :target].each do |param| + options.delete(param) if options.include?(param) end - # LAK:FIXME This shouldn't be necessary, but as long as we're - # modeling the relationship graph specifically, it is. - catalog.relationship_graph.add_edge self, child - - return child + return self.class.create(options) end # Files handle paths specially, because they just lengthen their @@ -672,70 +508,121 @@ module Puppet @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") end - # Recurse into the directory. This basically just calls 'localrecurse' - # and maybe 'sourcerecurse', returning the collection of generated - # files. + def make_children(metadata) + metadata.collect { |meta| newchild(meta.relative_path) } + end + + # Recursively generate a list of file resources, which will + # be used to copy remote files, manage local files, and/or make links + # to map to another directory. def recurse - # are we at the end of the recursion? - return unless self.recurse? - - recurse = self[:recurse] - # we might have a string, rather than a number - if recurse.is_a?(String) - if recurse =~ /^[0-9]+$/ - recurse = Integer(recurse) - else # anything else is infinite recursion - recurse = true - end + children = recurse_local + + if self[:target] + recurse_link(children) + elsif self[:source] + recurse_remote(children) end - if recurse.is_a?(Integer) - recurse -= 1 + return children.values.sort { |a, b| a[:path] <=> b[:path] } + end + + # A simple method for determining whether we should be recursing. + def recurse? + return false unless @parameters.include?(:recurse) + + val = @parameters[:recurse].value + + if val and (val == true or val > 0) + return true + else + return false end - - children = [] - - # We want to do link-recursing before normal recursion so that all - # of the target stuff gets copied over correctly. - if @parameters.include? :target and ret = self.linkrecurse(recurse) - children += ret + end + + # Recurse the target of the link. + def recurse_link(children) + perform_recursion(self[:target]).each do |meta| + if meta.relative_path == "." + self[:ensure] = :directory + next + end + + children[meta.relative_path] ||= newchild(meta.relative_path) + if meta.ftype == "directory" + children[meta.relative_path][:ensure] = :directory + else + children[meta.relative_path][:ensure] = :link + children[meta.relative_path][:target] = meta.full_path + end + end + children + end + + # Recurse the file itself, returning a Metadata instance for every found file. + def recurse_local + result = perform_recursion(self[:path]) + return {} unless result + result.inject({}) do |hash, meta| + next hash if meta.relative_path == "." + + hash[meta.relative_path] = newchild(meta.relative_path) + hash end - if ret = self.localrecurse(recurse) - children += ret + end + + # Recurse against our remote file. + def recurse_remote(children) + sourceselect = self[:sourceselect] + + total = self[:source].collect do |source| + next unless result = perform_recursion(source) + result.each { |data| data.source = "%s/%s" % [source, data.relative_path] } + break result if result and ! result.empty? and sourceselect == :first + result + end.flatten + + # This only happens if we have sourceselect == :all + unless sourceselect == :first + found = [] + total.reject! do |data| + result = found.include?(data.relative_path) + found << data.relative_path unless found.include?(data.relative_path) + result + end end - # These will be files pulled in by the file source - sourced = false - if @parameters.include?(:source) - ret, sourced = self.sourcerecurse(recurse) - if ret - children += ret + total.each do |meta| + if meta.relative_path == "." + property(:source).metadata = meta + next end + children[meta.relative_path] ||= newchild(meta.relative_path) + children[meta.relative_path][:source] = meta.source + children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file" + + children[meta.relative_path].property(:source).metadata = meta end - # The purge check needs to happen after all of the other recursion. + # If we're purging resources, then delete any resource that isn't on the + # remote system. if self.purge? - children.each do |child| - if (sourced and ! sourced.include?(child[:path])) or ! child.managed? + # Make a hash of all of the resources we found remotely -- all we need is the + # fast lookup, the values don't matter. + remotes = total.inject({}) { |hash, meta| hash[meta.relative_path] = true; hash } + + children.each do |name, child| + unless remotes.include?(name) child[:ensure] = :absent end end end - + children end - # A simple method for determining whether we should be recursing. - def recurse? - return false unless @parameters.include?(:recurse) - - val = @parameters[:recurse].value - - if val and (val == true or val > 0) - return true - else - return false - end + def perform_recursion(path) + Puppet::FileServing::Metadata.search(path, :links => self[:links], :recurse => self[:recurse], :ignore => self[:ignore]) end # Remove the old backup. @@ -796,108 +683,22 @@ module Puppet # a wrapper method to make sure the file exists before doing anything def retrieve unless stat = self.stat(true) - # If the file doesn't exist but we have a source, then call - # retrieve on that property propertyvalues = properties().inject({}) { |hash, property| hash[property] = :absent hash } + # If the file doesn't exist but we have a source, then call + # retrieve on the source property so it will set the 'should' + # values all around. if @parameters.include?(:source) - propertyvalues[:source] = @parameters[:source].retrieve + @parameters[:source].copy_source_values end return propertyvalues end - return currentpropvalues() - end - - # This recurses against the remote source and makes sure the local - # and remote structures match. It's run after 'localrecurse'. This - # method only does anything when its corresponding remote entry is - # a directory; in that case, this method creates file objects that - # correspond to any contained remote files. - def sourcerecurse(recurse) - # we'll set this manually as necessary - if @arghash.include?(:ensure) - @arghash.delete(:ensure) - end - - r = false - if recurse - unless recurse == 0 - r = 1 - end - end - - ignore = self[:ignore] - - result = [] - found = [] - - # Keep track of all the files we found in the source, so we can purge - # appropriately. - sourced = [] - - success = false - - @parameters[:source].should.each do |source| - sourceobj, path = uri2obj(source) - - # okay, we've got our source object; now we need to - # build up a local file structure to match the remote - # one - - server = sourceobj.server - - desc = server.list(path, self[:links], r, ignore) - if desc == "" - next - end - - success = true - - # Now create a new child for every file returned in the list. - result += desc.split("\n").collect { |line| - file, type = line.split("\t") - next if file == "/" # skip the listing object - name = file.sub(/^\//, '') - - # This makes sure that the first source *always* wins - # for conflicting files. - next if found.include?(name) - - # For directories, keep all of the sources, so that - # sourceselect still works as planned. - if type == "directory" - newsource = @parameters[:source].should.collect do |tmpsource| - tmpsource + file - end - else - newsource = source + file - end - args = {:source => newsource} - if type == file - args[:recurse] = nil - end - - found << name - sourced << File.join(self[:path], name) - - self.newchild(name, false, args) - }.reject {|c| c.nil? } - - if self[:sourceselect] == :first - return [result, sourced] - end - end - - unless success - raise Puppet::Error, "None of the provided sources exist" - end - - return [result, sourced] + currentpropvalues() end # Set the checksum, from another property. There are multiple diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb index 2514d3d1e..e43706051 100755 --- a/lib/puppet/type/file/source.rb +++ b/lib/puppet/type/file/source.rb @@ -1,3 +1,7 @@ + +require 'puppet/file_serving/content' +require 'puppet/file_serving/metadata' + 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 @@ -62,8 +66,14 @@ module Puppet uncheckable validate do |source| - unless @resource.uri2obj(source) - raise Puppet::Error, "Invalid source %s" % source + begin + uri = URI.parse(URI.escape(source)) + rescue => detail + self.fail "Could not understand source %s: %s" % [source, detail.to_s] + end + + unless uri.scheme.nil? or %w{file puppet}.include?(uri.scheme) + self.fail "Cannot use URLs of type '%s' as source for fileserving" % [uri.scheme] end end @@ -77,72 +87,67 @@ module Puppet end def change_to_s(currentvalue, newvalue) - # newvalue = "{md5}" + @stats[:checksum] + # newvalue = "{md5}" + @metadata.checksum if @resource.property(:ensure).retrieve == :absent - return "creating from source %s with contents %s" % [@source, @stats[:checksum]] + return "creating from source %s with contents %s" % [metadata.source, @metadata.checksum] else - return "replacing from source %s with contents %s" % [@source, @stats[:checksum]] + return "replacing from source %s with contents %s" % [metadata.source, @metadata.checksum] end end def checksum - if defined?(@stats) - @stats[:checksum] + if defined?(@metadata) + @metadata.checksum else nil end end - # Ask the file server to describe our file. - def describe(source) - sourceobj, path = @resource.uri2obj(source) - server = sourceobj.server + # Look up (if necessary) and return remote content. + def content + raise Puppet::DevError, "No source for content was stored with the metadata" unless metadata.source - begin - desc = server.describe(path, @resource[:links]) - rescue Puppet::Network::XMLRPCClientError => detail - fail detail, "Could not describe %s: %s" % [path, detail] + unless defined?(@content) and @content + unless tmp = Puppet::FileServing::Content.find(@metadata.source) + fail "Could not find any content at %s" % @metadata.source + end + @content = tmp.content end + @content + end - return nil if desc == "" + # Copy the values from the source to the resource. Yay. + def copy_source_values + devfail "Somehow got asked to copy source values without any metadata" unless metadata - # Collect everything except the checksum - values = desc.split("\t") - other = values.pop - args = {} - pinparams.zip(values).each { |param, value| - if value =~ /^[0-9]+$/ - value = value.to_i - end - unless value.nil? - args[param] = value + # Take each of the stats and set them as states on the local file + # if a value has not already been provided. + [:owner, :mode, :group, :checksum].each do |param| + next if param == :owner and Puppet::Util::SUIDManager.uid != 0 + unless value = @resource[param] and value != :absent + @resource[param] = metadata.send(param) end - } - - # Now decide whether we're doing checksums or symlinks - if args[:type] == "link" - args[:target] = other - else - args[:checksum] = other end - # we can't manage ownership unless we're root, so don't even try - unless Puppet::Util::SUIDManager.uid == 0 - args.delete(:owner) + @resource[:ensure] = metadata.ftype + + if metadata.ftype == "link" + @resource[:target] = metadata.destination end - - return args end - - # Use the info we get from describe() to check if we're in sync. + + # Remove any temporary attributes we manage. + def flush + @metadata = nil + @content = nil + end + + # Use the remote metadata to see if we're in sync. + # LAK:NOTE This method should still get refactored. def insync?(currentvalue) - 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" + unless @metadata.ftype == "file" return true end @@ -153,15 +158,13 @@ module Puppet 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])) + result = (!parentchecksum.nil? and (parentchecksum == @metadata.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. - 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 + if ! result and Puppet[:show_diff] and File.exists?(@resource[:path]) + string_file_diff(@resource[:path], content) end return result end @@ -171,57 +174,39 @@ module Puppet end def found? - ! (@stats.nil? or @stats[:type].nil?) + ! (@metadata.nil? or @metadata.ftype.nil?) 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 + # Provide, and retrieve if necessary, the metadata for this file. Fail + # if we can't find data about this host, and fail if there are any + # problems in our query. + attr_writer :metadata + def metadata + unless defined?(@metadata) and @metadata + return @metadata = nil unless should + should.each do |source| + begin + if data = Puppet::FileServing::Metadata.find(source) + @metadata = data + @metadata.source = source + break + end + rescue => detail + fail detail, "Could not retrieve file metadata for %s: %s" % [source, detail] end - } - end - - if !found? - raise Puppet::Error, "No specified source was found from" + @should.inject("") { |s, source| s + " #{source},"}.gsub(/,$/,"") - end - - case @stats[:type] - when "directory", "file", "link": - @resource[:ensure] = @stats[:type] unless @resource.deleting? - else - self.info @stats.inspect - self.err "Cannot use files of type %s as sources" % @stats[:type] - return :nocopy + end + fail "Could not retrieve information from source(s) %s" % @should.join(", ") unless @metadata end + return @metadata + 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? - @resource[stat] = value unless @resource.argument?(stat) - } - - return @stats[:checksum] + # Just call out to our copy method. Hopefully we'll refactor 'source' to + # be a parameter soon, in which case 'retrieve' is unnecessary. + def retrieve + copy_source_values end + # Return the whole array, rather than the first item. def should @should end @@ -238,11 +223,9 @@ module Puppet end def sync - contents = @stats[:_remote_content] || get_remote_content() - - exists = File.exists?(@resource[:path]) + exists = FileTest.exist?(@resource[:path]) - @resource.write(contents, :source, @stats[:checksum]) + @resource.write(content, :source, @metadata.checksum) if exists return :file_changed @@ -250,27 +233,5 @@ module Puppet return :file_created end end - - private - - def get_remote_content - raise Puppet::DevError, "Got told to copy non-file %s" % @resource[:path] unless @stats[:type] == "file" - - sourceobj, path = @resource.uri2obj(@source) - - begin - contents = sourceobj.server.retrieve(path, @resource[:links]) - rescue => detail - self.fail "Could not retrieve %s: %s" % [path, detail] - end - - contents = CGI.unescape(contents) unless sourceobj.server.local - - if contents == "" - self.notice "Could not retrieve contents for %s" % @source - end - - return contents - end end end |