
Conditional Statements
Prerequisites
- Welcome
- Power of Puppet
- Resources
- Manifests and Classes
- Modules
- Variables and Class Parameters
Quest Objectives
- Learn how to use conditional logic to make your manifests adaptable.
- Understand the syntax and function of the
if
,unless
,case
, andselector
statements.
Getting Started
Conditional statements allow you to write Puppet code that will return different values or execute different blocks of code depending on conditions you specify. In conjunction with Facter, which makes details of a machine available as variables, this lets you write Puppet code that flexibly accomodates different platforms, operating systems, and functional requirements.
To start this quest enter the following command:
quest --start conditionals
Writing for Flexibility
The green reed which bends in the wind is stronger than the mighty oak which breaks in a storm.
-Confucius
Because Puppet manages configurations on a variety of systems fulfilling a variety of roles, great Puppet code means flexible and portable Puppet code. While the types and providers that form the core of Puppet's resource abstraction layer do a lot of heavy lifting around this kind of adaptation, there are some things better left in the hands of competent practitioners rather than hard-coded in Puppet itself.
As you move from general platform-related implementation details to specific application-related implementation details, it starts making less sense to rely on Puppet to make decisions automatically, and much more sense for a module developer or user to make his or her own choices.
It's sensible, for example, for Puppet's package
providers to take care of
installing and maintaining packages. The inputs and outputs are standardized and
stable enough that what happens in between, as long as it happens reliably, can
be safely hidden by abstraction; once it's done, the details are no longer
important.
What package is installed, on the other hand, isn't something you can safely forget. In this case, the inputs and outputs are not so neatly delimited. Though there are often broadly equivalent packages for different platforms, the equivalence isn't always complete; configuration details will often vary, and these details will likely have to be accounted for elsewhere in your Puppet module.
While Puppet's built-in providers can't themselves guarantee the portability of your Puppet code at this higher level of implementation, Puppet's DSL gives you the tools to build adaptability into your modules. facts and conditional statements are the bread and butter of this functionality.
Facts
Get your facts first, then distort them as you please.
-Mark Twain
You already encountered the facter tool when we asked you to run
facter ipaddress
in the setup section of this quest guide. While it's nice the
be able to run facter from the command line, its real utility is to make
information about a system available to use as variables in your manifests.
While facter is an important component of Puppet and is bundled with Puppet Enterprise, it's actually one of the many separate open-source projects integrated into the Puppet ecosystem.
Combined with conditionals, which we'll get to in a moment, facts give you a huge amount of power to write portability into your modules.
To get a full list of facts available to facter, enter the command:
facter -p | less
Any of the facts you see listed here can be used within your Puppet code with
the syntax $::factname
. The double colons ::
indicate that the fact is
defined in what's called top scope, that is, before any variables in your node
definitions or classes are assigned. While you could technically use a fact
without the ::
, you would risk having it mistakenly overridden by a locally
defined variable with the same name. The ::
tells Puppet to look directly in
the top scope instead of using the first variable it finds with a matching name.
The Puppet style guide suggests using this syntax consistently to avoid naming
collisions.
Conditions
Just dropped in (to see what condition my condition was in)
-Mickey Newbury
Conditional statements return different values or execute different blocks of code depending on the value of a specified variable. This is key to getting your Puppet modules to perform as desired on machines running different operating systems and fulfilling different roles in your infrastructure.
Puppet supports a few different ways of implementing conditional logic:
-
if
statements, -
unless
statements, - case statements, and
- selectors.
The 'if' Statement
Puppet’s if
statements behave much like those in many other programming and
scripting languages.
An if
statement includes a condition followed by a block of Puppet code that
will only be executed if that condition evaluates as true. Optionally,
an if
statement can also include any number of elsif
clauses and an else
clause. Here are some rules:
- If the
if
condition fails, Puppet moves on to theelsif
condition (if one exists). - If both the
if
andelsif
conditions fail, Puppet will execute the code in theelse
clause (if one exists). - If all the conditions fail, and there is no
else
block, Puppet will do nothing and move on.
Lets say you want to give the user you're creating with your accounts module
administrative priveleges. You have a mix of CentOS and Debian systems in your
infrastructure. On your CentOS machines, you use the wheel
group to manage
superuser privileges, while you use an admin
group on the Debian machines.
With the if
statement and the operatingsystem
fact from facter, this kind of
adjustment is easy to automate with Puppet.
Before getting started, make sure you're working in the modules
directory:
cd /etc/puppetlabs/puppet/environments/production/modules
Task 1 :
Create an accounts
directory:
mkdir accounts
And your tests
and manifests
directories:
mkdir accounts/{manifests,tests}
Task 2 :
Open the accounts/manifests/init.pp
manifest in Vim.
At the beginning of the accounts
class definition, you'll include some
conditional logic to set the $groups
variable based on the value of the
$::operatingsystem
fact. In both cases, you'll add the user the new group made
solely for that user, defined by the $name
parameter. If the operating system
is CentOS, you'll also add the user to the wheel
group, and if the operating
system is Debian you'll add the user to the admin
group.
So the beginning of your class definition should looks something like this:
class accounts ($name) {
if $::operatingsystem == 'centos' {
$groups = 'wheel'
}
elsif $::operatingsystem == 'debian' {
$groups = 'admin'
}
else {
fail( "This module doesn't support ${::operatingsystem}." )
}
notice ( "Groups for user ${name} set to ${groups}" )
...
}
Note that the string matches are not case sensitive, so 'CENTOS' would work
just as well as 'centos'. Finally, in the else
block, you'll raise a warning
that the module doesn't support the current OS.
Once you've written the conditional logic to set the $groups
variable, edit
the user
resource declaration to assign the $groups
variable to the groups
attribute.
class accounts ($name) {
...
user { $name:
ensure => 'present',
home => "/home/${name}",
groups => $groups,
}
...
}
Make sure that your manifest can pass a puppet parser validate
check before
continuing on.
Task 3 :
Create a test manifest (accounts/tests/init.pp
) and declare the accounts
manifest with the name parameter set to dana
.
class {'accounts':
name => 'dana',
}
Task 4 :
The Learning VM is running CentOS, so to test what would happen on a Debian OS
you'll have to override the operatingsystem
fact with a little environment
variable magic. To provide a custom value for any facter fact as you run a
puppet apply
, you can include FACTER_factname=new_value
before your new
terminal command.
Combining this with the --noop
flag, you can do a quick test of how your
manifest would run on a different system before setting up a full testing
environment.
FACTER_operatingsystem=Debian puppet apply --noop accounts/tests/init.pp
Look in the list of notices, and you'll see the changes that would have been applied.
Task 5 :
Try one more time with an unsupported operating system to check the fail condition:
FACTER_operatingsystem=Darwin puppet apply --noop accounts/tests/init.pp
Task 6 :
Now go ahead and run a puppet apply --noop
on your test manifest without
setting the environment variable. If this looks good, drop the --noop
flag to
apply the catalog generated from your manifest.
You can use the puppet resource
tool to verify the results.
The 'unless' Statement
The unless
statement works like a reversed if
statement. An unless
statements takes a condition and a block of Puppet code. It will only execute
the block if the condition is false. If the condition is true, Puppet
will do nothing and move on. Note that there is no equivalent of elsif
or
else
clauses for unless
statements.
The 'case' Statement
Like if
statements, case statements choose one of several blocks of Puppet
code to execute. Case statements take a control expression, a list of cases, and
a series of Puppet code blocks that correspond to those cases. Puppet will
execute the first block of code whose case value matches the control expression.
A special default
case matches anything. It should always be included at the
end of a case statement to catch anything that did not match an explicit case.
For instance, if you were setting up an Apache webserver, you might use a case statement like the following:
case $::operatingsystem {
'CentOS': { $apache_pkg = 'httpd' }
'Redhat': { $apache_pkg = 'httpd' }
'Debian': { $apache_pkg = 'apache2' }
'Ubuntu': { $apache_pkg = 'apache2' }
default: { fail("Unrecognized operating system for webserver.") }
}
package { $apache_pkg :
ensure => present,
}
This would allow you to always install and manage the right Apache package for a machine's operating system. Accounting for the differences between various platforms is an important part of writing flexible and re-usable Puppet code, and it's a paradigm you will encounter frequently in published Puppet modules.
The 'selector' Statement
Selector statements are similar to case
statements, but instead of executing a
block of code, a selector assigns a value directly. A selector might look
something like this:
$rootgroup = $::osfamily ? {
'Solaris' => 'wheel',
'Darwin' => 'wheel',
'FreeBSD' => 'wheel',
'default' => 'root',
}
Here, the value of the $rootgroup
is determined based on the control variable
$::osfamily
. Following the control variable is a ?
(question mark) symbol.
In the block surrounded by curly braces are a series of possible values for the
$::osfamily
fact, followed by the value that the selector should return if the
value matches the control variable.
Because a selector can only return a value and cannot execute a function like
fail()
or warning()
, it is up to you to make sure your code handles
unexpected conditions gracefully. You wouldn't want Puppet to forge ahead with
an inappropriate default value and encounter errors down the line.
Review
In this quest, you saw how you can use facts from the facter
tool along with
conditional logic to write Puppet code that will adapt to the envoronment where
you're applying it.
You used an if
statement in conjunction with the $::osfamily
variable from
facter to determine how to set the group for an administrator user account.
We also covered a few other forms of conditional statement: 'unless', the case statement, and the selector. Though there aren't any hard-and-fast rules for which conditional statement is best in a given situation, there will generally be one that results in the most concise and readible code. It's up to you to decide what works best.