On May 27, 2009, at 7:45 AM, James Turnbull wrote:

>
> From: Bart Vanbrabant <[email protected]>
>
>
> Signed-off-by: James Turnbull <[email protected]>
> ---
> lib/puppet/parser/compiler.rb                |   29 ++++++--
> lib/puppet/parser/conflicthandler.rb         |   95 +++++++++++++++++ 
> +++++++++
> lib/puppet/parser/conflicthandlers/concat.rb |   39 +++++++++++
> lib/puppet/type.rb                           |   11 +++
> 4 files changed, 167 insertions(+), 7 deletions(-)
> create mode 100644 lib/puppet/parser/conflicthandler.rb
> create mode 100644 lib/puppet/parser/conflicthandlers/concat.rb
>
> diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/ 
> compiler.rb
> index 968e0b9..ee0f098 100644
> --- a/lib/puppet/parser/compiler.rb
> +++ b/lib/puppet/parser/compiler.rb
> @@ -33,13 +33,26 @@ class Puppet::Parser::Compiler
>     def add_resource(scope, resource)
>         @resources << resource
>
> -        # Note that this will fail if the resource is not unique.
> -        @catalog.add_resource(resource)
> -
> -        # And in the resource graph.  At some point, this might  
> supercede
> -        # the global resource table, but the table is a lot faster
> -        # so it makes sense to maintain for now.
> -        @catalog.add_edge(scope.resource, resource)
> +        begin
> +            # Note that this will fail if the resource is not unique.
> +            @catalog.add_resource(resource)
> +
> +            # And in the resource graph.  At some point, this might  
> supercede
> +            # the global resource table, but the table is a lot  
> faster
> +            # so it makes sense to maintain for now.
> +            @catalog.add_edge(scope.resource, resource)
> +        rescue Puppet::Resource::Catalog::DuplicateResourceError =>
> +                conflict_exception
> +            # get the original resource the new one is conflicting  
> with
> +            original = @catalog.resource(resource.ref)
> +
> +            # Ask the conflicthandler if there is a handler defined
> +            handler =  
> Puppet::Parser::ConflictHandler.get_handler(original)
> +            raise conflict_exception unless handler
> +
> +            # invoke the handler and ignore the duplicate resource
> +            handler.add_conflict(resource)

This is actually much harder to get correct than it appears, because  
of how resource overrides work.  That is, resources that are equal at  
definition time might become unequal later.  E.g., two classes define  
equivalent but conflicting resources, your conflict handler does  
whatever handling it needs to do, then a subclass of one of the  
classes modifies one of the resources such that they're no longer equal.

This is the reason why Puppet doesn't just ignore equal resources --  
equal is essentially a point in time until the very end, and if we  
wait that long to do conflict checking then we're looking at a  
significant shift in how conflict checking is done internally.

(No further comments below, figure we can focus on these first few  
comments for now.)

>
> +        end
>     end
>
>     # Do we use nodes found in the code, vs. the external node  
> sources?
> @@ -374,6 +387,8 @@ class Puppet::Parser::Compiler
>
>             resource.finish if resource.respond_to?(:finish)
>         end
> +
> +        Puppet::Parser::ConflictHandler.finish()
>     end
>
>     # Initialize the top-level scope, class, and resource.
> diff --git a/lib/puppet/parser/conflicthandler.rb b/lib/puppet/ 
> parser/conflicthandler.rb
> new file mode 100644
> index 0000000..bdbb947
> --- /dev/null
> +++ b/lib/puppet/parser/conflicthandler.rb
> @@ -0,0 +1,95 @@
> +# A conflict handler
> +module Puppet::Parser
> +    class ConflictHandler
> +       class << self
> +            include Puppet::Util
> +
> +            # setup autoloading for all conflicthandlers
> +            def autoloader
> +                unless defined? @autoloader
> +                    @autoloader = Puppet::Util::Autoload.new(self,
> +                        "puppet/parser/conflicthandlers",
> +                        :wrap => false)
> +                end
> +                @autoloader
> +            end
> +
> +            def handlers
> +                autoloader.loadall
> +
> +                @handlers
> +            end
> +
> +            # a class method to add a new conflicthandler. The  
> first argument is
> +            # the name of the resource type the handler can handle.  
> The name is the
> +            # name of the conflicthandlers that is referenced in  
> the metaparameter.
> +            # The block is that actual code that handles the  
> conflict.
> +            def newconflicthandler(type, name, &block)
> +                @handlers ||= {}
> +                type = symbolize(type)
> +
> +                if handler(name, type)
> +                    raise Puppet::DevError, "Conflict handler for  
> %s already defined" % type
> +                end
> +
> +                # create a new class
> +                clazz = Class.new ConflictHandler
> +                clazz.add_finish(&block)
> +
> +                # store the class
> +                @handlers[name] ||= {}
> +                @handlers[name][type] = clazz
> +            end
> +
> +            # Does a handler exist with the given name and type
> +            def handler(name, type)
> +                return (handlers.has_key? name and  
> handlers[name].has_key? type)
> +            end
> +
> +            # Get the handler for the given resource. If there  
> isn't one create
> +            # one if this resource has a handler defined
> +            def get_handler(resource)
> +                @instances ||= {}
> +
> +                if @instances.has_key? resource.ref
> +                    return @instances[resource.ref]
> +                else
> +                    handler = resource["conflicthandler"]
> +                    type = resource.type.downcase.to_sym
> +                    return unless handler(handler, type)
> +
> +                    cls = @handlers[handler][type]
> +                    instance = cls.new(resource)
> +                    @instances[resource.ref] = instance
> +
> +                    return instance
> +                end
> +            end
> +
> +            def finish
> +                @instances.each do |ref, obj|
> +                    obj.finish
> +                end if @instances
> +
> +                # clear all instances
> +                @instances = {}
> +            end
> +
> +            # add the finish method to this class
> +            def add_finish(&block)
> +                send(:define_method, :finish, &block)
> +            end
> +        end
> +
> +        attr_reader :conflicts, :resource
> +        def initialize(resource, &block)
> +            @resource = resource
> +            @conflicts = []
> +        end
> +
> +        def add_conflict(resource)
> +            @conflicts << resource
> +        end
> +    end
> +end
> +
> diff --git a/lib/puppet/parser/conflicthandlers/concat.rb b/lib/ 
> puppet/parser/conflicthandlers/concat.rb
> new file mode 100644
> index 0000000..38ce12a
> --- /dev/null
> +++ b/lib/puppet/parser/conflicthandlers/concat.rb
> @@ -0,0 +1,39 @@
> +# Concatenate the content of the duplicate file to the original file
> +# TODO: handle owner, group, mode, ... parameters
> +class Puppet::Parser::ConflictHandler
> +    self.newconflicthandler("file", "concat") do
> +        # find the content parameter in the kept resource
> +        content_param = nil
> +        resource.eachparam do |param|
> +            if param.name == :content
> +                content_param = param
> +            elsif param.name == :source
> +                raise ArgumentError.new("Can not concat file  
> resources that have the source parameter set")
> +            end
> +        end
> +
> +        # collect all the values of the content parameter
> +        values = []
> +
> +        values << content_param.value if content_param
> +        conflicts.each do |conflict|
> +            if conflict[:source]
> +                raise ArgumentError.new("Can not concat file  
> resources that have the source parameter set")
> +            end
> +
> +            value = conflict[:content]
> +            values << value if value
> +        end
> +
> +        # sort them to prevent flapping
> +        values.sort!
> +
> +        # set the value
> +        if content_param
> +            content_param.value = values.join
> +        else
> +            resource.set_parameter(:content, values.join)
> +        end
> +    end
> +end
> +
> diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
> index 99ac909..4ca2c04 100644
> --- a/lib/puppet/type.rb
> +++ b/lib/puppet/type.rb
> @@ -13,6 +13,7 @@ require 'puppet/resource/reference'
> require 'puppet/util/cacher'
> require 'puppet/file_collection/lookup'
> require 'puppet/util/tagging'
> +require 'puppet/parser/conflicthandler'
>
> # see the bottom of the file for the rest of the inclusions
>
> @@ -1439,6 +1440,16 @@ class Type
>             This will restart the sshd service if the sshd config  
> file changes.}
>     end
>
> +    newmetaparam(:conflicthandler) do
> +        desc %{The name of the conflicthandler that is invoked if a  
> conflicting
> +            resource exists.}
> +
> +        validate do |handler|
> +            Puppet::Parser::ConflictHandler.handler(handler,  
> @resource.class.name)
> +        end
> +    end
> +
> +
>     ###############################
>     # All of the provider plumbing for the resource types.
>     require 'puppet/provider'
> -- 
> 1.6.0.6
>
>
> >


-- 
I was an only child... eventually. -- Stephen Wright
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to