Writing Puppet Types

This tutorial will only cover a small portion of what is entailed in the creation of puppet types and will focus on creating custom types, as opposed to creating types which integrate directly into the puppet source tree.

Puppet types are the backbone of puppet code, every stanza in puppet manifests is an expression of a puppet type. In a perfect “puppet” world every aspect of the puppet code would be expressed in the form of puppet types and there would be no need for “exec” or even “file” in puppet. Unfortunately that time is still a few years away. But for now it is perfectly viable for us to create and distribute our own puppet types!

Installing puppet types

All of the custom puppet types are distributed in the puppet tree, the default way to distribute puppet types is to include them in a subdirectory of any puppet module. The subdirectory needs to be named lib/puppet/type/. So if the module was named “base” and the modulepath was “/var/lib/puppet/modules”, then the ruby source code file containing the custom type would be placed in “/var/lib/puppet/modules/base/lib/puppet/type/”. Now the type will be distributed to all of the puppet nodes when pluginsync is executed at the beginning of each puppet run.

The parts of a puppet type

The puppet type itself can be thought of as having two high level containers, and the two containers each have a few mandatory parts. The two “containers” are the “Type Definition” and the “Type Provider”. Since this is a simple introduction to puppet types only a single provider will be covered, but keep in mind that many providers can be (and in the puppet source code almost always are) created.

The type definition is a very simple component to implement, begin by extending the Puppet module and calling the newtype method, passing a ruby symbol as the argument and a block of containing the definition. Since all that mumbo jumbo sounds funny, and rubyish, here is an example from a custom sysctrl type:

1 module Puppet

2 newtype(:sysctl) do

3 @doc = “Manages the sysctl interface for unix-like systems.

4 The sysctl module works primarily by managing the /etc/sysctl.conf

5 file, and then by calling the ‘sysctl -p’ command to apply the state

6 of the /etc/sysctl.conf file.

7

8 This is a very simple type and only makes use of a few paramaters.

9 The type only supports three paramaters, the namevar paramater, name,

10 is the dot notation reference to the desired sysctl setting, aka

11 ‘vm.swappiness’. The value paramater is always a string and is the

12 value to pass to the gives sysctl setting. The sysctl trype is also

13 ensurable, so all rules need to have the regular ensure => present

14 option set.

15

16 A typical rule will look like this:

17

18 sysctl {‘vm.swappiness’:

19 ensure => present,

20 value => ’20’,

21 }

22

23 This rule would ensure that the kernel swappiness setting be set to ’20′”

24

25 ensurable

26

27 newparam(:name, :namevar => true) do

28 desc “The name of the variable in /proc/sys given in dot notation, eg vm.swappiness”

29 end

30 newparam(:value) do

31 desc “The value to enforce for the sys variable.”

32 end

33 end

34 end

35

On line 1 the “module Puppet” statement opens up the Puppet module for extending and opens up the top level code block. On line 2 the newtype method is called with “:sysctl” as the argument. This defines the name of the new type to be sysctl. The newtype method is followed by a do to start out a code block used to define the new type.

Lines 3 through 23 defines the type’s doc string. Like all good code the type needs to be documented, typically this is the place to define how the type is used inside a puppet manifest.

Line 25 is one of the most important single statements in the type. This includes all of the ensurable behavior, in particular it includes the “ensure” parameter so commonly seen in puppet types.

Lines 27 and 30 are of particular interest, and these are really the meat of the type definition. The newparam method will add a parameter to the type which will allow information to be entered for the type. The information is then passed from the manifest on to the provider. The newparam method is very straightforward, pass a symbol with the name of the parameter, start a code block and set the description. The “name” parameter has an extra argument, the “:namevar => true”, this argument can only be used on a single parameter and sets the special namevar value to the parameter. This means that the name passed to the type in the manifest will populate this field. Finally, this is ruby, so, end, end, end.

The type provider is where the actual work needs to happen, puppet needs to know how to do what it needs to do for a specific platform. In this example the only provider supplied is for Linux based systems, but if desired a slightly different provider could be made for FreeBSD which could read the kernel stack defaults when returning a setting to a system default.

The creation of a provider is done by opening up the Puppet module, accessing the type that was just created, and calling the provide method. Inside the provide method access to the values entered in the puppet manifest are made available via the @resource object, which can be read like a ruby hash.

Since puppet is engineered to enforce system states, the state of the system must be assessed, and then commands to run if the state of the system is not consistent with the desired state must be available. This is the “exists? create destroy” approach to puppet types, and will work for most situations.

The code block started by calling the provide method needs to contain these three methods, exists?, create and destroy. The exists method must return true or false, as the return value will be used to determine whether to do nothing or to create of destroy the setting. The exists? Method is executed on the client assigned the puppet rule with every puppet run and should in no situation make any alterations to the system, it MUST be completely read-only!

When the exists? method returns the value passed to ensure is evaluated. If exists? returns false and the ensure value is present then the create method will be executed, if exists? returns true and the ensure value is absent then the destroy method is called.

So this is very straightforward, exists? Determines the state, create creates and destroy destroys! Here is the code that makes it happen:
36 Puppet::Type.type(:sysctl).provide(:sysctl_linux) do

37 desc “Support for managing the linux kernel stack”

38

39 def create

40 lines = File.new(‘/etc/sysctl.conf’, ‘r’).readlines

41 done = false

42 lines.each_index do |i|

43 if lines[i].split(‘=’)[0].strip == @resource[:name]

44 lines[i] = “#{@resource[:name]} = #{@resource[:value]}\n”

45 done = true

46 end

47 end

48 unless done

49 lines << “#{@resource[:name]} = #{@resource[:value]}\n”

50 end

51 sysfile = File.new(‘/etc/sysctl.conf’, ‘w’)

52 for line in lines

53 sysfile.write(line)

54 end

55 sysfile.close

56 `sysctl -p`

57 end

58

59 def destroy

60 lines = File.new(‘/etc/sysctl.conf’, ‘r’).readlines

61 lines.each_index do |i|

62 if lines[i].split(‘=’)[0].strip == @resource[:name]

63 lines[i] = “”

64 end

65 sysfile = File.new(‘/etc/sysctl.conf’, ‘w’)

66 for line in lines

67 sysfile.write(line)

68 end

69 sysfile.close

70 `sysctl -p`

71 end

72 end

73

74 def exists?

75 lines = File.new(‘/etc/sysctl.conf’, ‘r’).readlines

76 lines.each do |line|

77 if line.split(‘=’)[0].strip == @resource[:name]

78 if line.split(‘=’)[1].strip == @resource[:value]

79 return true

80 elsif @resource[:ensure] == :absent

81 return true

82 end

83 end

84 end

85 return false

86 end

87 end

In a custom type it is often simplest to include the provider in the same ruby file as the type definition, whereas when writing puppet types for upstream puppet they should be separated into individual files in their respective locations.

The 36th line is the initial invocation of the code block used to set the provider to the type, this is where the :sysctl type is accessed and the provide method is called with the respective code block.

Line 74 defines the exists? Method. As previously mentioned, exists? is implemented in a completely non-invasive way and only returns a boolean.

Lines 39 and 59 start the definition of the create and destroy methods. It is noteworthy that the destroy method returns the system to as exact a state as possible when executed!

Hopefully this is enough to help someone begin their journey into creating puppet types, there is a great deal more worth covering, including deeper access into the @resource object, the commands execution interface, the parsed file module and raising puppet exceptions.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: