ptomulik-macro
Table of Contents
- Overview
- Module Description
- Usage
- Example 1: Defining macro in ruby code
- Example 2: Invoking macro in puppet manifest
- Example 3: Macro with parameters
- Example 4: Variable number of parameters
- Example 5: Default parameters
- Example 6: Invoking macro from macro
- Example 7: Using variables
- Example 8: Building dependencies between parameters
- Reference
- Limitations
Overview
Puppet parser macros.
Module Description
With this functionality module developers may define named macros and evaluate
them in manifests. This works similarly to parser
functions but
implementing a macro is a little bit easier. Also, macros may use
"hierarchical" names in foo::bar::geez
form, so a module developer may easily
establish one-to-one correspondence between macro names and class/define
parameters.
The main reason for this module being developed is exemplified in Example 8.
Usage
Example 1: Defining macro in ruby code
To define a macro named foo::bar in a module write a file named lib/puppet/macros/foo/bar.rb:
# lib/puppet/macros/foo/bar.rb
Puppet::Macros.newmacro 'foo::bar' do ||
'macro foo::bar'
end
The above macro simply returns the 'macro foo::bar'
string. Note the empty
argument list ||
. This enforces strict arity checking (zero arguments) on
ruby 1.8. Without ||
the block is assumed to accept arbitrary number of
arguments on 1.8 and no arguments on 1.9.
Example 2: Invoking macro in puppet manifest
Nothing simpler than:
$foo_bar = determine('foo::bar')
notify { foo_bar: message => "determine('foo::bar') -> ${foo_bar}" }
If you don't need the value returned by macro, then you may invoke macro as a statement:
invoke('foo::bar')
Example 3: Macro with parameters
Let's define macro sum2
which adds two integers:
# lib/puppet/macros/sum2.rb
Puppet::Macros.newmacro 'sum2' do |x,y|
Integer(x) + Integer(y)
end
Now sum2
may be used as follows:
$sum = determine('sum2', 1, 2)
notify { sum: message => "determine('sum2',1,2) -> ${sum}" }
Example 4: Variable number of parameters
Let's redefine macro from Example 3 to accept arbitrary number of parameters:
# lib/puppet/macros/sum.rb
Puppet::Macros.newmacro 'sum' do |*args|
args.map{|x| Integer(x)}.reduce(0,:+)
end
Now, few experiments:
$zero = determine('sum')
$one = determine('sum',1)
$three = determine('sum',1,2)
notify { zero: message => "determine('sum') -> ${zero}" }
notify { one: message => "determine('sum',1) -> ${one}" }
notify { three: message => "determine('sum',1,2) -> ${three}" }
Example 5: Default parameters
Default parameters work only with ruby 1.9+. If you don't care about compatibility with ruby 1.8, you may define a macro with default parameters in the usual way:
# lib/puppet/macros/puppet/config/content.rb
Puppet::Macros.newmacro 'puppet::config::content' do |file='/etc/puppet/puppet.conf'|
File.read(file)
end
Now you may use it with:
$content = determine('puppet::config::content')
notify { content: message => $content }
or
$content = determine('puppet::config::content','/usr/local/etc/puppet/puppet.conf')
notify { content: message => $content }
If you need the same for ruby 1.8, here is a workaround (note that the caller i.e the determine function, will check the minimum arity, so we only check the maximum):
# lib/puppet/macros/puppet/config/content.rb
Puppet::Macros.newmacro 'puppet::config::content' do |*args|
if args.size > 1
raise Puppet::ParseError, "Wrong number of arguments (#{args.size} for maximum 1)"
end
args << '/etc/puppet/puppet.conf' if args.size < 1
File.read(args[0])
end
Example 6: Invoking macro from macro
You may invoke macro using call_macro
method:
# lib/puppet/macros/bar.rb
Puppet::Macros.newmacro 'bar' do
call_macro('foo::bar')
end
The first argument to call_macro
is the name of the macro to be invoked, the
second (if present) is an array of arguments to be passed to macro.
You may alternatively use function interface, but this isn't the recommended way (you may receive misleading exception messages in case you mess up with arguments to macro).
# lib/puppet/macros/bar.rb
Puppet::Macros.newmacro 'bar' do
function_determine(['foo::bar'])
end
If you test any of the above with the following puppet code:
$bar = determine('bar')
notify { bar: message => "determine('bar') -> ${bar}" }
then the following notice would appear on output:
Notice: determine('bar') -> macro foo::bar
Obviously the above text is the result of foo::bar
macro defined in
Example 1.
Example 7: Using variables
You may access puppet variables, for example $::osfamily
(fact). The
following example determines default location of apache configs for operating
system running on slave:
# lib/puppet/macros/apache/conf_dir.rb
Puppet::Macros.newmacro 'apache::conf_dir' do
case os = lookupvar("::osfamily")
when /FreeBSD/; '/usr/local/etc/apache22'
when /Debian/; '/usr/etc/apache2'
else
raise Puppet::Error, "unsupported osfamily #{os.inspect}"
end
end
$apache_conf_dir = determine('apache::conf_dir')
notify { apache_conf_dir: message => "determine('apache::conf_dir') -> ${apache_conf_dir}" }
Example 8: Building dependencies between parameters
Macros may be used to inter-depend parameters of defined types or classes. In
other words, if one parameter is altered by user, others should be adjusted
automatically, unless user specify them explicitly. For example, we may have a
defined type testmodule::foo
with two parameters $a
and $b
and we want
$b
to depend on $a
. So, we may define macros testmodule::foo::a
and
testmodule::foo::b
, and testmodule::foo::b
may accept $a
as an argument:
# lib/puppet/macros/testmodule/foo/a.rb
Puppet::Macros.newmacro 'testmodule::foo::a' do |a|
pp2r(a) ? a : 'default a'
end
# lib/puppet/macros/testmodule/foo/b.rb
Puppet::Macros.newmacro 'testmodule::foo::b' do |b, a|
pp2r(b) ? b : "default b for a=#{a.inspect}"
end
Then, if we split testmodule::foo
into actual implementation (let say
testmodule::impl::foo
) and a wrapper (let's call it simply testmodule::foo
),
the job may be finished as follows:
# manifests/impl/foo.pp
define testmodule::impl::foo($a, $b) {
notify{$title: message => "${title}: a=\'${a}\', b=\'${b}\'"}
}
# manifests/foo.pp
define testmodule::foo($a = undef, $b = undef)
{
$_a = determine('testmodule::foo::a', $a)
$_b = determine('testmodule::foo::b', $b, $_a)
testmodule::impl::foo{"$title":
a => $_a,
b => $_b
}
}
Now, the following manifest
puppet apply --modulepath $(pwd) <<!
testmodule::foo {defaults: }
testmodule::foo {custom_a: a => 'custom a' }
testmodule::foo {custom_b: b => 'custom b' }
testmodule::foo {custom_a_and_b: a => 'custom a', b => 'custom b' }
testmodule::foo {other: }
Testmodule::Foo[other] { a => 'other default a' }
!
would output these lines:
Notice: defaults: a='default a', b='default b for a="default a"'
Notice: custom_a: a='custom a', b='default b for a="custom a"'
Notice: custom_b: a='default a', b='custom b'
Notice: custom_a_and_b: a='custom a', b='custom b'
Notice: other: a='other default a', b='default b for a="other default a"'
Reference
Function reference
Index of functions:
determine
Determine value of a macro.
This function ivokes a macro defined with Puppet::Macros.newmacro
method and returns its value. The function takes macro name as first
argument and macro parameters as the rest of arguments. The number of
arguments provided by user is validated against macro's arity.
Example:
Let say, you have defined the following macro in puppet/macros/sum.rb:
# puppet/macros/sum.rb
Puppet::Macros.newmacro 'sum' do |x,y|
Integer(x) + Integer(y)
end
You may then invoke the macro from puppet as follows:
$three = determine('sum',1,2) # -> 3
- Type: rvalue
[Index of functions|Table of Contents]
invoke
Invoke macro as a statement.
This function ivokes a macro defined with Puppet::Macros.newmacro
method. The function takes macro name as first argument and macro parameters
as the rest of arguments. The number of arguments provided by user is
validated against macro's arity.
Example:
Let say, you have defined the following macro in puppet/macros/print.rb:
# puppet/macros/pring.rb
Puppet::Macros.newmacro 'print' do |msg|
print msg
end
You may then invoke the macro from puppet as follows:
invoke('print',"hello world!\\n")
- Type: statement
[Index of functions|Table of Contents]
API Reference
API reference may be generated with
bundle exec rake yard
The generated documentation goes to doc/
directory. Note that this works only
under ruby >= 1.9.
The API documentation is also available online.
Limitations
- Currently there is no possibility to define macro in puppet manifests, that is
we only can define macro using ruby and use it in ruby or puppet. I believe
this functionality may implemented as an additional parser function (call it
macro
) and it should work with the help of puppet lambdas, which are available in future parser. - Currently there is no way to store and auto-generate documentation for macros. It should work similarly as for functions but its not implemented at the moment. This may be added in future versions.