Creating Puppet manifests
We covered the Puppet DSL syntax that is used in the Puppet manifests. Let's try to create a manifest and learn how to apply it to the system.
Note
The simplest way to create a manifest is to use the puppet resource
command to create the resource definition and redirect the output of the command to the manifest file.
The following are the steps to create a manifest:
- Use the
puppet resource
command to declare a user resource and redirect the command output to a file using a single greater than character>
followed by the filename:puppet resource user Jakob > user.pp
This command won't return any message to the screen as you have redirected the command output to a file called
user.pp
. - Before we inspect the contents of the
user.pp
file, let's add another user definition touser.pp
with the following commands. This time, the output redirection is done using the double greater than characters>>
. The difference between the single and the double greater than characters is how the output file is managed. The>
character overwrites the file contents if the file already exists, while the>>
characters append to the file:puppet resource user Markus >> user.pp
- Let's take a look at the content of the
user.pp
file. To view the content, we can open the file in the text editor. Linux systems usually come with multiple text editors, such as Vi, but we'll use another editor called Nano, which is easier to use than Vi. - You can open the
user.pp
file in the Nano text editor by typing the following command:nano user.pp
- You will see that the
user.pp
file contains two user definitions: the first definition is for the userJakob
and the second definition is for the userMarkus
. Currently, both the resources have theensure
attribute value asabsent
, which corresponds to the current state of the user accounts on the system.Here is the content of the file in the Nano text editor:
user { 'Jakob': ensure => 'absent', } user { 'Markus': ensure => 'absent', }
- Using the arrow keys on the keyboard, you can move the cursor around the text file. Change both the
ensure
attribute values topresent
. - Once the
ensure
attribute values for both the user resources have been changed, the content of the file should be as follows:user { 'Jakob': ensure => 'present', } user { 'Markus': ensure => 'present', }
- Now press Ctrl + X on the keyboard and save the changes by pressing Y and then Enter.
Well done! You have just created your first manifest file that manages two resources. Now it's time to apply the manifest with the following command:
puppet apply user.pp
The following is the output generated by the preceding command:
You must have probably noticed that this time we ran the puppet apply command without the -–execute
option. The --execute
option is only used to provide the manifest content from the command line. Now that we have created the manifest file, and if we want to apply it, the --execute
option can be omitted. Typically, the --execute
option is used to pass parameters to the Puppet class that is declared in the manifest. We will discuss the Puppet classes more in detail later on in this book.
Idempotency
Let's run the command again, and you will notice the difference in the command output compared to the previous Puppet run:
puppet apply user.pp
This time, the output is shorter. The lines that notify us that the users Jakob
and Markus
were created are missing in this Puppet run:
Notice: Compiled catalog for learning.puppetlabs.vm in environment production in 0.14 seconds Notice: Finished catalog run in 0.27 seconds
This is due to the idempotent nature of Puppet. As the users Jakob
and Markus
already exist in the system, Puppet doesn't attempt to recreate these accounts. Idempotency in Puppet means that you can apply the same manifest as many times as you like, and only when the state of the resource in the system is different from the state of the resource declared in the manifest, will Puppet ensure that the required configuration changes are performed according to the manifest.
To demonstrate how Puppet handles idempotency, we will remove the user Markus
with the following command, which we are familiar with:
puppet resource user Markus ensure=absent
Then, apply the manifest again with the puppet apply user.pp
command, and you can see that the user Jakob
, which we did not remove earlier, does not appear in the output but the user Markus
is recreated.
Here is the command again:
puppet apply user.pp
The output of the command is as follows:
Notice: Compiled catalog for learning.puppetlabs.vm in environment production in 0.15 seconds Notice: /Stage[main]/Main/User[Markus]/ensure: created Notice: Finished catalog run in 0.35 seconds
Puppet command line versus Puppet manifests
So far, we have practiced how to manage system resources from the command line with puppet resource command, and also learned how to manage resources with the Puppet manifest and puppet apply command. When we start expanding our environment with new hosts and increase the number of resources that Puppet manages on these hosts, you will notice that the Puppet command line doesn't scale very well. The Puppet command line typically manages a single resource, such as user Elisa
or user Jakob
. Each of these resources was created with its own command. If I have 100 user accounts to be managed, then that would result in the same amount of commands to be run, which would be a very tiring job for anyone to do.
Puppet is a configuration management and automation tool that helps you eliminate repetitive tasks, such as creating 100 user accounts. Instead of running the puppet resource command 100 times, it is better if we add all our users once to a single manifest file, call the file with a puppet apply command, and let the Puppet do the hard work for us. Puppet manifests are types of recipes for your system configuration. Once you have described your system configuration in the form of a manifest, you can easily transfer the recipe onto another host and apply the configuration with a single command.
Managing files and directories with a file resource
The phrase "everything is a file" that is often associated with the Linux operating system makes it an ideal environment for Puppet to manage. Puppet is very good at managing files. Puppet's file resource can create files from static source files. You can define the file content with the content
attribute, or you can create files with a dynamic content using templates. A file resource can also be used to manage directories and links.
The syntax of a file resource is very similar to the user resource syntax, only the set of available attributes is different. Here is a simple example of how to create an empty directory called /root/Documents
:
file { '/root/Documents': ensure => directory; }
The first line defines the type of resource we want Puppet to manage, followed by the name of the directory that Puppet creates.
The ensure
attribute on line two says that the file resource must be a directory. If we omit the ensure
attribute, Puppet will create a file instead of a directory.
The closing curly brace }
on the third and the last line ends the file resource statement.
Let's do a practical experiment with the file resource and write a manifest file that sets a log in greeting message when the user Jakob
logs in. In order to do this, our manifest must fulfill the following two criteria:
- The user
Jakob
must have a home directory to store the login greeting message - The user
Jakob
must have a custom.bash_profile
file present under the home directory
To start with, let's remove the user Jakob
from the system so that we can easily recreate the account with a password, and tell Puppet to create a home directory for the user:
puppet resource user Jakob ensure=absent
Now when the user Jakob
has been removed, let's generate a user resource for Jakob
and redirect the output to the file called jakobs-login.pp
. Again, we use the single >
character to create a new file:
puppet resource user Jakob > jakobs-login.pp
Then, using the >>
notation to redirect the output to the jakobs-login.pp
file, we can generate the file resource snippet for the .bash_profile
file that will be placed under the home directory of Jakob
with the following command:
puppet resource file /home/Jakob/.bash_profile >> jakobs-login.pp
Now that we have the manifest body ready for editing, we can open the jakobs-login.pp
file in the Nano editor:
nano jakobs-login.pp
On opening the file, you should see the following file content:
user { 'Jakob': ensure => 'absent', } file { '/home/Jakob/.bash_profile': ensure => 'absent', }
Let's begin by changing the ensure
attribute value from absent
to present
for the user resource Jakob
.
Then, to tell Puppet to create the home directory for the user, we need to use the managehome
attribute and set its value to true
. The managehome
attribute is specific to a user resource, and we can use it to tell Puppet to create a home directory for the user under the /home
directory. The home directory is needed to store the .bash_profile
file, which we will take a look at shortly.
Finally, to enable Jakob
to log in using the password puppet
, we should set the encrypted password
attribute value to $1$jrm5tnjw$h8JJ9mCZLmJvIxvDLjw1M/
.
This is how the user resource for Jakob
should look like after the changes:
user { 'Jakob': ensure => 'present', managehome => true, password => '$1$jrm5tnjw$h8JJ9mCZLmJvIxvDLjw1M/', }
Before we move on to the file resource, let's save the changes with Ctrl + X and hit Enter. Then, apply the manifest to the puppet apply
command:
puppet apply jakobs-login.pp
If the Puppet run was successful, you should see the following output:
Notice: Compiled catalog for learning.puppetlabs.vm in environment production in 0.63 seconds Notice: /Stage[main]/Main/User[Jakob]/ensure: created Notice: /Stage[main]/Main/File[/home/Jakob/.bash_profile]/ensure: removed Notice: Finished catalog run in 0.35 seconds
If we take a look at the third line of the output, we can see that Puppet removed the /home/Jakob/.bash_profile
file although we had not yet created it. This is because of the managehome
attribute that we declared for the user Jakob
, which results in the Linux environment to create the file when the user is created. Because we haven't yet modified the file resource for /home/Jakob/.bash_profile
in the manifest, the ensure
attribute value is absent
. This results in Puppet removing the file.
Don't worry, as we will now tell Puppet to recreate the file with the content that we specify:
- Open the
jakobs-login.pp
manifest file using the Nano editor using the following command:nano jakobs-login.pp
- Using the arrow keys, move to the file resource that currently has the following content:
file { '/home/Jakob/.bash_profile': ensure => 'absent', }
- Instead of updating the
ensure
attribute value fromabsent
topresent
, we can remove the attribute altogether and replace it with thecontent
attribute. To greet the userJakob
with his name when he logs in, we can specify thecontent
attribute in the following way:file { '/home/Jakob/.bash_profile': content => 'echo Hello $(logname)', }
- When you are done with the changes, you can save the file using Ctrl + X and press Enter.
- Now let's apply the most recent changes from the manifest;
puppet apply jakobs-login.pp
The output is as follows:
Notice: Compiled catalog for learning.puppetlabs.vm in environment production in 0.22 seconds Notice:/Stage[main]/Main/File[/home/Jakob/.bash_profile]/ensure: defined content as '{md5}7af0d63debeedf19adbd8bb239f5ab36' Notice: Finished catalog run in 0.53 seconds
- Now, it is the big moment to test whether our configuration changes work as expected. Log out with the
exit
command and then log in as userJakob
using the passwordpuppet
.If the configuration changes were successful, you should see the bottom of the login banner, showing the message
Hello Jakob
.