diff --git a/lib/puppet/network/format_handler.rb b/lib/puppet/network/format_handler.rb index d378ad6a1..b94a4f902 100644 --- a/lib/puppet/network/format_handler.rb +++ b/lib/puppet/network/format_handler.rb @@ -1,175 +1,181 @@ require 'yaml' require 'puppet/network' require 'puppet/network/format' module Puppet::Network::FormatHandler class FormatError < Puppet::Error; end class FormatProtector attr_reader :format def protect(method, args) Puppet::Network::FormatHandler.format(format).send(method, *args) rescue => details direction = method.to_s.include?("intern") ? "from" : "to" error = FormatError.new("Could not #{method} #{direction} #{format}: #{details}") error.set_backtrace(details.backtrace) raise error end def initialize(format) @format = format end [:intern, :intern_multiple, :render, :render_multiple, :mime].each do |method| define_method(method) do |*args| protect(method, args) end end end @formats = {} def self.create(*args, &block) instance = Puppet::Network::Format.new(*args) instance.instance_eval(&block) if block_given? @formats[instance.name] = instance instance end + def self.create_serialized_formats(name,options = {},&block) + ["application/x-#{name}", "application/#{name}", "text/x-#{name}", "text/#{name}"].each { |mime_type| + create name, {:mime => mime_type}.update(options), &block + } + end + def self.extended(klass) klass.extend(ClassMethods) # LAK:NOTE This won't work in 1.9 ('send' won't be able to send # private methods, but I don't know how else to do it. klass.send(:include, InstanceMethods) end def self.format(name) @formats[name.to_s.downcase.intern] end def self.format_by_extension(ext) @formats.each do |name, format| return format if format.extension == ext end nil end # Provide a list of all formats. def self.formats @formats.keys end # Return a format capable of handling the provided mime type. def self.mime(mimetype) mimetype = mimetype.to_s.downcase @formats.values.find { |format| format.mime == mimetype } end # Use a delegator to make sure any exceptions generated by our formats are # handled intelligently. def self.protected_format(name) name = format_to_canonical_name(name) @format_protectors ||= {} @format_protectors[name] ||= FormatProtector.new(name) @format_protectors[name] end # Return a format name given: # * a format name # * a mime-type # * a format instance def self.format_to_canonical_name(format) case format when Puppet::Network::Format out = format when %r{\w+/\w+} out = mime(format) else out = format(format) end raise ArgumentError, "No format match the given format name or mime-type (#{format})" if out.nil? out.name end module ClassMethods def format_handler Puppet::Network::FormatHandler end def convert_from(format, data) format_handler.protected_format(format).intern(self, data) end def convert_from_multiple(format, data) format_handler.protected_format(format).intern_multiple(self, data) end def render_multiple(format, instances) format_handler.protected_format(format).render_multiple(instances) end def default_format supported_formats[0] end def support_format?(name) Puppet::Network::FormatHandler.format(name).supported?(self) end def supported_formats result = 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 result = put_preferred_format_first(result) Puppet.debug "#{friendly_name} supports formats: #{result.map{ |f| f.to_s }.sort.join(' ')}; using #{result.first}" result end private def friendly_name if self.respond_to? :indirection indirection.name else self end end def put_preferred_format_first(list) preferred_format = Puppet.settings[:preferred_serialization_format].to_sym if list.include?(preferred_format) list.delete(preferred_format) list.unshift(preferred_format) else Puppet.debug "Value of 'preferred_serialization_format' (#{preferred_format}) is invalid for #{friendly_name}, using default (#{list.first})" end list end end module InstanceMethods def render(format = nil) format ||= self.class.default_format Puppet::Network::FormatHandler.protected_format(format).render(self) end def mime(format = nil) format ||= self.class.default_format Puppet::Network::FormatHandler.protected_format(format).mime end def support_format?(name) self.class.support_format?(name) end end end require 'puppet/network/formats' diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb index 8a61ee62a..4ca3240d4 100644 --- a/lib/puppet/network/formats.rb +++ b/lib/puppet/network/formats.rb @@ -1,162 +1,162 @@ require 'puppet/network/format_handler' -Puppet::Network::FormatHandler.create(:yaml, :mime => "text/yaml") do +Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do # Yaml doesn't need the class name; it's serialized. def intern(klass, text) YAML.load(text) end # Yaml doesn't need the class name; it's serialized. def intern_multiple(klass, text) YAML.load(text) end def render(instance) instance.to_yaml end # Yaml monkey-patches Array, so this works. def render_multiple(instances) instances.to_yaml end # Unlike core's yaml, ZAML should support 1.8.1 just fine def supported?(klass) true end end # This is a "special" format which is used for the moment only when sending facts # as REST GET parameters (see Puppet::Configurer::FactHandler). # This format combines a yaml serialization, then zlib compression and base64 encoding. -Puppet::Network::FormatHandler.create(:b64_zlib_yaml, :mime => "text/b64_zlib_yaml") do +Puppet::Network::FormatHandler.create_serialized_formats(:b64_zlib_yaml) do require 'base64' def use_zlib? Puppet.features.zlib? && Puppet[:zlib] end def requiring_zlib if use_zlib? yield else raise Puppet::Error, "the zlib library is not installed or is disabled." end end def intern(klass, text) decode(text) end def intern_multiple(klass, text) decode(text) end def render(instance) encode(instance.to_yaml) end def render_multiple(instances) encode(instances.to_yaml) end def supported?(klass) true end def encode(text) requiring_zlib do Base64.encode64(Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION)) end end def decode(yaml) requiring_zlib do YAML.load(Zlib::Inflate.inflate(Base64.decode64(yaml))) end end end Puppet::Network::FormatHandler.create(:marshal, :mime => "text/marshal") do # Marshal doesn't need the class name; it's serialized. def intern(klass, text) Marshal.load(text) end # Marshal doesn't need the class name; it's serialized. def intern_multiple(klass, text) Marshal.load(text) end def render(instance) Marshal.dump(instance) end # Marshal monkey-patches Array, so this works. def render_multiple(instances) Marshal.dump(instances) end # Everything's supported def supported?(klass) true end end Puppet::Network::FormatHandler.create(:s, :mime => "text/plain", :extension => "txt") # 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 -Puppet::Network::FormatHandler.create(:pson, :mime => "text/pson", :weight => 10, :required_methods => [:render_method, :intern_method]) do +Puppet::Network::FormatHandler.create_serialized_formats(:pson, :weight => 10, :required_methods => [:render_method, :intern_method]) do confine :true => Puppet.features.pson? def intern(klass, text) data_to_instance(klass, PSON.parse(text)) end def intern_multiple(klass, text) PSON.parse(text).collect do |data| data_to_instance(klass, data) end end # PSON monkey-patches Array, so this works. def render_multiple(instances) instances.to_pson end # If they pass class information, we want to ignore it. By default, # we'll include class information but we won't rely on it - we don't # want class names to be required because we then can't change our # internal class names, which is bad. def data_to_instance(klass, data) if data.is_a?(Hash) and d = data['data'] data = d end return data if data.is_a?(klass) klass.from_pson(data) end end # This is really only ever going to be used for Catalogs. -Puppet::Network::FormatHandler.create(:dot, :mime => "text/dot", :required_methods => [:render_method]) +Puppet::Network::FormatHandler.create_serialized_formats(:dot, :required_methods => [:render_method])