Conditional statements
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 accommodates different platforms, operating systems, and functional requirements.
To start this quest enter the following command:
quest begin conditional_statements
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 many things best left in the hands of competent practitioners.
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, it really shows its worth on the
back end, making 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
You can reference any of the facts you see listed here with the same syntax
you would use for a variable you had assigned within your manifest. There
is one notable difference, however. Because facts for a node are available
in any manifest compiled for that node, they exist somewhere called top scope.
This means that though a fact can be accessed anywhere, it can also be overwritten
by any variable of the same name in a lower scope (e.g. in node or class scope).
To avoid potential collisions, it's best to explicitly scope references to facts.
You specify top scope by prepending your factname with double colons ::
(pronounced "scope scope"). So a fact in your manifest should look like this:
$::factname
.
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.
Because the same concept underlies these different modes of conditional logic,
we'll only cover the if
statement in the tasks for this quest. Once you have
a good understanding of how to implement if
statements, we'll leave you with
descriptions of the other forms and some notes on when you may find them useful.
If
Puppet’s if
statements behave much like those in 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.
- 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.
Let's say you want to give the user you're creating with your accounts
module
administrative privileges. 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 you get started writing your module, make sure you're working in the
modules
directory:
cd /etc/puppetlabs/code/environments/production/modules
Task 1:
Create an accounts
directory and your examples
and manifests
directories:
mkdir -p accounts/{manifests,examples}
Task 2:
Open the accounts/manifests/init.pp
manifest in Vim.
At the beginning of the accounts
class definition, you'll include
conditional logic to set the $groups
variable based on the value of the
$::operatingsystem
fact. If the operating system is CentOS, Puppet will
add the user to the wheel
group, and if the operating system is Debian,
Puppet will ad the user to the admin
group.
The beginning of your class definition should look like this:
class accounts ($user_name) {
if $::operatingsystem == 'centos' {
$groups = 'wheel'
}
elsif $::operatingsystem == 'debian' {
$groups = 'admin'
}
else {
fail( "This module doesn't support ${::operatingsystem}." )
}
notice ( "Groups for user ${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 an error
if the module doesn't support the current OS.
Once you've written the conditional logic to set the $groups
variable, create
a user
resource declaration. Use the $user_name
variable set by your class parameter
to set the title and home of your user, and use the $groups
variable to set the
user's groups
attribute.
class accounts ($user_name) {
...
user { $user_name:
ensure => present,
home => "/home/${user_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/examples/init.pp
) and declare the accounts
manifest with the name parameter set to dana
.
class {'accounts':
user_name => 'dana',
}
Task 4:
The Learning VM is running CentOS, but to test our conditional logic,
we want to see what would happen on a Debian system. Luckily, we can use
a little environment variable magic to override the operatingsystem
fact
for a test run. To provide a custom value for any facter fact as you run a
puppet apply
, you can include FACTER_factname=new_value
before your command.
Combine this with the --noop
flag, to do a quick test of how your
manifest would run on a different system.
FACTER_operatingsystem=Debian puppet apply --noop accounts/examples/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/examples/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.
Unless
The unless
statement works like a reversed if
statement. An unless
statement 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.
Case
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.
While your other cases will often be strings with surrounding quotation marks,
the default
case is a bare word without surrounding quotation marks.
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.
Selector
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 environment where
you're applying it.
You used an if
statement in conjunction with the $::operatingsystem
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 readable code. It's up to you to
decide what works best.