Update to the latest version. #3

Merged
merged 1 commit into from Jun 24, 2011
View
3 README
@@ -1,6 +1,9 @@
- License - Apache Version 2.0
- Copyright - Puppetlabs 2011
+*NOTE* - this has exists in 2.7.x core, it has been published seperately
+so that it can be used with 2.6.x
+
This module contains a custom function for puppet that can be used to dynamically add resources to the catalog.
I wrote this to use with an external node classifier that consumes YAML.
View
47 lib/puppet/parser/functions/create_resources.rb
@@ -1,32 +1,47 @@
Puppet::Parser::Functions::newfunction(:create_resources, :doc => '
-Converts a hash into resources and adds them to the catalog.
+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=>{attr=>value}}
+ {title=>{parameters}}
+ This is currently tested for defined resources, classes, as well as native types
') do |args|
- raise ArgumentError, 'requires resource type and param hash' if args.size < 2
+ 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|
- # TODO - add argument to specify constraints on parameters
raise ArgumentError, 'params should not contain title' if(params['title'])
- if type = Puppet::Type.type(args[0].to_sym)
- resource = type.hash2resource(params.merge(:title => title))
- catalog.add_resource(resource)
- elsif args[0].downcase == 'class'# || args[0].downcase == 'node'
- klass = find_hostclass(title)
- klass.ensure_in_catalog(self, params)
- compiler.catalog.add_class([title])
- else
- # TODO - use ensure_in_catalog when it supports definitions
- # we assume that if nothing else mathces is must be a defined resource type
- resource = find_definition(args[0])
- p_resource = Puppet::Parser::Resource.new(args[0], title, :scope => self, :source => resource)
+ 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
View
6 spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
View
18 spec/spec_helper.rb
@@ -0,0 +1,18 @@
+require 'pathname'
+dir = Pathname.new(__FILE__).parent
+$LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib')
+
+require 'mocha'
+require 'puppet'
+gem 'rspec', '=1.2.9'
+require 'spec/autorun'
+
+Spec::Runner.configure do |config|
+ config.mock_with :mocha
+end
+
+# We need this because the RAL uses 'should' as a method. This
+# allows us the same behaviour but with a different method name.
+class Object
+ alias :must :should
+end
View
137 spec/unit/puppet/parser/functions/create_resources_spec.rb
@@ -0,0 +1,137 @@
+require 'puppet'
+require '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
+ Puppet::Parser::Functions.function(:create_resources)
+ end
+
+ it "should exist" do
+ Puppet::Parser::Functions.function(:create_resources).should == "function_create_resources"
+ end
+ it 'should require two arguments' do
+ lambda { @scope.function_create_resources(['foo']) }.should raise_error(ArgumentError, 'create_resources(): wrong number of arguments (1; must be 2)')
+ end
+ describe 'when creating native types' do
+ before :each do
+ Puppet[:code]='notify{test:}'
+ get_scope
+ @scope.resource=Puppet::Parser::Resource.new('class', 't', :scope => @scope)
+ end
+ it 'empty hash should not cause resources to be added' do
+ @scope.function_create_resources(['file', {}])
+ @compiler.catalog.resources.size == 1
+ end
+ it 'should be able to add' do
+ @scope.function_create_resources(['file', {'/etc/foo'=>{'ensure'=>'present'}}])
+ @compiler.catalog.resource(:file, "/etc/foo")['ensure'].should == 'present'
+ end
+ it 'should accept multiple types' do
+ type_hash = {}
+ type_hash['foo'] = {'message' => 'one'}
+ type_hash['bar'] = {'message' => 'two'}
+ @scope.function_create_resources(['notify', type_hash])
+ @compiler.catalog.resource(:notify, "foo")['message'].should == 'one'
+ @compiler.catalog.resource(:notify, "bar")['message'].should == 'two'
+ end
+ it 'should fail to add non-existing type' do
+ lambda { @scope.function_create_resources(['foo', {}]) }.should raise_error(ArgumentError, 'could not create resource of unknown type foo')
+ end
+ it 'should be able to add edges' do
+ @scope.function_create_resources(['notify', {'foo'=>{'require' => 'Notify[test]'}}])
+ @scope.compiler.compile
+ rg = @scope.compiler.catalog.to_ral.relationship_graph
+ test = rg.vertices.find { |v| v.title == 'test' }
+ foo = rg.vertices.find { |v| v.title == 'foo' }
+ test.should be
+ foo.should be
+ rg.path_between(test,foo).should be
+ end
+ end
+ describe 'when dynamically creating resource types' do
+ before :each do
+ Puppet[:code]=
+'define foo($one){notify{$name: message => $one}}
+notify{test:}
+'
+ get_scope
+ @scope.resource=Puppet::Parser::Resource.new('class', 't', :scope => @scope)
+ Puppet::Parser::Functions.function(:create_resources)
+ end
+ it 'should be able to create defined resoure types' do
+ @scope.function_create_resources(['foo', {'blah'=>{'one'=>'two'}}])
+ # still have to compile for this to work...
+ # I am not sure if this constraint ruins the tests
+ @scope.compiler.compile
+ @compiler.catalog.resource(:notify, "blah")['message'].should == 'two'
+ end
+ it 'should fail if defines are missing params' do
+ @scope.function_create_resources(['foo', {'blah'=>{}}])
+ lambda { @scope.compiler.compile }.should raise_error(Puppet::ParseError, 'Must pass one to Foo[blah] at line 1')
+ end
+ it 'should be able to add multiple defines' do
+ hash = {}
+ hash['blah'] = {'one' => 'two'}
+ hash['blaz'] = {'one' => 'three'}
+ @scope.function_create_resources(['foo', hash])
+ # still have to compile for this to work...
+ # I am not sure if this constraint ruins the tests
+ @scope.compiler.compile
+ @compiler.catalog.resource(:notify, "blah")['message'].should == 'two'
+ @compiler.catalog.resource(:notify, "blaz")['message'].should == 'three'
+ end
+ it 'should be able to add edges' do
+ @scope.function_create_resources(['foo', {'blah'=>{'one'=>'two', 'require' => 'Notify[test]'}}])
+ @scope.compiler.compile
+ rg = @scope.compiler.catalog.to_ral.relationship_graph
+ test = rg.vertices.find { |v| v.title == 'test' }
+ blah = rg.vertices.find { |v| v.title == 'blah' }
+ test.should be
+ blah.should be
+ # (Yoda speak like we do)
+ rg.path_between(test,blah).should be
+ @compiler.catalog.resource(:notify, "blah")['message'].should == 'two'
+ end
+ end
+ describe 'when creating classes' do
+ before :each do
+ Puppet[:code]=
+'class bar($one){notify{test: message => $one}}
+notify{tester:}
+'
+ get_scope
+ @scope.resource=Puppet::Parser::Resource.new('class', 't', :scope => @scope)
+ Puppet::Parser::Functions.function(:create_resources)
+ end
+ it 'should be able to create classes' do
+ @scope.function_create_resources(['class', {'bar'=>{'one'=>'two'}}])
+ @scope.compiler.compile
+ @compiler.catalog.resource(:notify, "test")['message'].should == 'two'
+ @compiler.catalog.resource(:class, "bar").should_not be_nil#['message'].should == 'two'
+ end
+ it 'should fail to create non-existing classes' do
+ lambda { @scope.function_create_resources(['class', {'blah'=>{'one'=>'two'}}]) }.should raise_error(ArgumentError ,'could not find hostclass blah')
+ end
+ it 'should be able to add edges' do
+ @scope.function_create_resources(['class', {'bar'=>{'one'=>'two', 'require' => 'Notify[tester]'}}])
+ @scope.compiler.compile
+ rg = @scope.compiler.catalog.to_ral.relationship_graph
+ test = rg.vertices.find { |v| v.title == 'test' }
+ tester = rg.vertices.find { |v| v.title == 'tester' }
+ test.should be
+ tester.should be
+ rg.path_between(tester,test).should be
+ end
+ end
+end