ptomulik-macro

Build Status Coverage Status Code Climate

Table of Contents

  1. Overview
  2. Module Description
  3. Usage
  4. Reference
  5. Limitations

Overview

Puppet parser macros.

[Table of Contents]

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.

[Table of Contents]

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.

[Table of Contents]

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')

[Table of Contents]

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}" }

[Table of Contents]

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}" }

[Table of Contents]

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

[Table of Contents]

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.

[Table of Contents]

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}" }

[Table of Contents]

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"'

[Table of Contents]

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.

[Table of Contents]

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.

[Table of Contents]