On Thu, Mar 17, 2011 at 02:22, Dan Bode <bod...@gmail.com> wrote:

> This function allows you to dynamically generate resources,
> passing them as a hash to the create_resources function.
>
> This was originally written to be used together with an ENC.
>
> Resources can be programitally generated as yaml and passed to a class.

So, this is a pretty fundamental transformation of Puppet, I think.
We now have a declarative system, except where we use this sort of
programmatic set of inputs.  I would be happier if there was a regular
way to specify a class using YAML, or JSON, and this was a "run-time"
rather than "static" method to access that functionality.

Er, and this isn't actually true: anything that can generate a hash,
including writing it in the Puppet DSL, is the input.  This suggests
that it is limited, but we have really made a new mechanism that can
entirely bypass the regular DSL in favour of a data-focused version of
the same.

[…]
> Now I can dynamically determine how webserver instances are deployed to nodes
> by updating the YAML files.

Alternately, I would be happy to see machine-generated DSL from this
input data.  That would give you all the benefits of history tracking,
and wouldn't be enormously more complex than the current model, I
would have expected.  (Adding a shim between the master and the ENC
that generated this inline would be easy enough, I think.)


Anyway, practically we should ensure that the documentation for this
is as extensive and serious as the Puppet DSL for defining resources,
because it is totally going to have the same sort of problems and uses
that the existing DSL does.  (Actually, probably more, because we also
need "why would you use this instead of the regular DSL, and "please
don't work around the DSL with this")

> Signed-off-by: Dan Bode <bod...@gmail.com>
> ---
>  lib/puppet/parser/functions/create_resources.rb    |   47 +++++++
>  .../unit/parser/functions/create_resources_spec.rb |  135 
> ++++++++++++++++++++
>  2 files changed, 182 insertions(+), 0 deletions(-)
>  create mode 100644 lib/puppet/parser/functions/create_resources.rb
>  create mode 100755 spec/unit/parser/functions/create_resources_spec.rb
>
> diff --git a/lib/puppet/parser/functions/create_resources.rb 
> b/lib/puppet/parser/functions/create_resources.rb
> new file mode 100644
> index 0000000..430f110
> --- /dev/null
> +++ b/lib/puppet/parser/functions/create_resources.rb
> @@ -0,0 +1,47 @@
> +Puppet::Parser::Functions::newfunction(:create_resources, :doc => '
> +Converts a hash into a set of resources and adds them to the catalog.
> +Takes two parameters:
> +  create_resource($type, $resources)
> +    Creates resources of type $type from the $resources hash. Assumes that
> +    hash is in the following form:
> +     {title=>{parameters}}
> +  This is currently tested for defined resources, classes, as well as native 
> types
> +') do |args|
> +  raise ArgumentError, ("create_resources(): wrong number of arguments 
> (#{args.length}; must be 2)") if args.length != 2
> +  #raise ArgumentError, 'requires resource type and param hash' if args.size 
> < 2
> +  # figure out what kind of resource we are
> +  type_of_resource = nil
> +  type_name = args[0].downcase
> +  if type_name == 'class'
> +    type_of_resource = :class
> +  else
> +    if resource = Puppet::Type.type(type_name.to_sym)
> +      type_of_resource = :type
> +    elsif resource = find_definition(type_name.downcase)
> +      type_of_resource = :define
> +    else
> +      raise ArgumentError, "could not create resource of unknown type 
> #{type_name}"
> +    end
> +  end
> +  # iterate through the resources to create
> +  args[1].each do |title, params|
> +    raise ArgumentError, 'params should not contain title' 
> if(params['title'])
> +    case type_of_resource
> +    when :type
> +      res = resource.hash2resource(params.merge(:title => title))
> +      catalog.add_resource(res)
> +    when :define
> +      p_resource = Puppet::Parser::Resource.new(type_name, title, :scope => 
> self, :source => resource)
> +      params.merge(:name => title).each do |k,v|
> +        p_resource.set_parameter(k,v)
> +      end
> +      resource.instantiate_resource(self, p_resource)
> +      compiler.add_resource(self, p_resource)
> +    when :class
> +      klass = find_hostclass(title)
> +      raise ArgumentError, "could not find hostclass #{title}" unless klass
> +      klass.ensure_in_catalog(self, params)
> +      compiler.catalog.add_class([title])
> +    end
> +  end
> +end

The separation of logic about the "type" of resource, and the code
that creates it, is pretty ugly.  It would be much nicer if we could
avoid having two flow control structures in there.

Since this is a core patch, it would be nice to replace the logic in
the function with methods on the appropriate internal infrastructure
(Puppet::Resource) to create an instance from a hash – which this
could be a few lines using.

You are not doing any sanitization of input here, which you should be
for external inputs: at the very least, ensure that the keys in the
parameter hash are the appropriate basic types.  Ideally, we should
defend against having rich types created on either side, so that only
things that the DSL could create can be passed here.

> diff --git a/spec/unit/parser/functions/create_resources_spec.rb 
> b/spec/unit/parser/functions/create_resources_spec.rb
> new file mode 100755
> index 0000000..d4095b7
> --- /dev/null
> +++ b/spec/unit/parser/functions/create_resources_spec.rb
> @@ -0,0 +1,135 @@
> +require 'puppet'
> +require File.dirname(__FILE__) + '/../../../spec_helper'
> +
> +describe 'function for dynamically creating resources' do
> +
> +  def get_scope
> +    @topscope = Puppet::Parser::Scope.new
> +    # This is necessary so we don't try to use the compiler to discover our 
> parent.
> +    @topscope.parent = nil
> +    @scope = Puppet::Parser::Scope.new
> +    @scope.compiler = 
> Puppet::Parser::Compiler.new(Puppet::Node.new("floppy", :environment => 
> 'production'))
> +    @scope.parent = @topscope
> +    @compiler = @scope.compiler
> +  end
> +  before :each do
> +    get_scope

It would be nice to use the 'let' functionality around the get_scope
actions, rather than shoving stuff into member variables, both for
reducing long-term memory use, and to allow demand-drawn construction
of objects.

> +    Puppet::Parser::Functions.function(:create_resources)
> +  end
> +
> +  it "should exist" do
> +    Puppet::Parser::Functions.function(:create_resources).should == 
> "function_create_resources"
> +  end

This test is pretty much redundant: if it would fail, so would every
other test, so we might as well eliminate it.  (Not that I blame you:
the other files next to it perform the same next-to-useless test.)

Finally, there seem to be a set of similar tests in the rest of the
code: that edges can be added, etc.  It would be good to have only one
copy of that code, with appropriate data input to let the code test
it.  After all, if we find a bug in one part of those behaviours we
should really be checking it for every other top level type input,
right?

Thanks for the contribution,
    Daniel
-- 
⎋ Puppet Labs Developer – http://puppetlabs.com
✉ Daniel Pittman <dan...@puppetlabs.com>
✆ Contact me via gtalk, email, or phone: +1 (877) 575-9775
♲ Made with 100 percent post-consumer electrons

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

Reply via email to