Liveblogging: Pulling Strings with Puppet: A Gentle Introduction

I’m at the Professional IT Community Conference, aka PICC, for the first day.

I am taking Pulling Strings with Puppet: A Gentle Introduction by Thomas Uphill of the Institute for Advanced Study and Benjamin Rose – Princeton University…here are my notes!

Slides available at
example files

What is puppet?
It’s a language – you describe the state, not the steps to get there (declarative!) – paint a picture of your ideal system, and puppet does the rest.
Written in Ruby

Luke Kanies in 2005 developed it, company is – puppet Enterprise has commercial support, Windows support, etc.

Why do I want puppet?
It’s human-readable

Puppet has a rich history of versioning, they went from 0.20 -> 0.25 -> 2.6.0 -> 2.7.0 -> 2.7.14

Learning Puppet VM – – can get VMs for VMware, OVF, VirtualBox, KVM
They’re using the learn vm, puppet master is Jim, piggy and kermit are the client nodes

Resource Abstraction Layer
a bunch of types, exec, mount, service (something started from inet.d), file (just a file), package, user, group, host (/etc/hosts).

Types have providers, the providers take care of figuring out what kind of operating system you have and how to do it.

The core of the language is classes, variables and types. These apply to nodes – e.g. hosts.

Types, classes and variables can be put into modules, or plugins.

Syntax of types

type { "title":
attribute => value,
attribute => value

type is “exec”, “mount”, etc from above

basic example:
file {"testfile":
mode => 0644,
owner => root,
path => "/etc/test",
ensure => present

You can run a parser to make sure your syntax works:
puppet parser validate syntax.pp

Trifecta of package, file and service types (“core” types).

attributes: title
ensure – one of {present, absent, file, directory, link}
path – full path to the file, default is $title
source – url to the file, or local path
content – string contents of the file, or a template
target – any links
recurse – if this is a directory, you may want to copy everything in the dir (false by default)
purge – remove non-puppet file from the directory (true/false, false by default)
content –
file { "issue":
path => "/etc/motd",
content => "Hello World!"

file { "issue":
path => "/etc/motd"
content => file("/etc/hello")

file { "issue":
path => "/etc/motd",
content => template("/etc/hello.erb")

cat /etc/hello.erb
Hello !

If hostname is “learn”, it will say “Hello learn!”

puppet apply is the most basic way to apply changes from the manifest
puppet apply mymanifest.pp

Puppet can be a fileserver:
file { "issue":
path => "/etc/motd"
source => "puppet:///files/hello"

ensure – one of {present, latest, [version], absent, purged} latest will go and get the package. You might not want to do that if you’re using RH, b/c if you’re running puppet every half hour, it will run yum every half hour. purged takes any mentions of the package off, “absent” is safer
source – can provide location of the source

package { "sar":
name => 'sysstat',
ensure => '7.0.2-3.el5'

package { "sar":
name => 'sysstat',
ensure => '7.0.2-3.el5'
provider => rpm
source => '/root/sysstat-7.0.2-3.el5.i386.rpm'

service actually makes sure things are running
ensure – running, stopped, true, false (true=running, false=stopped)
enable – true, false (e.g. true is chkconfig foo on)
hasrestart – true, false (true means does a stop/start atomically)
hasstatus – true, false (true means it uses the “status” command used for the package, otherwise it does a pgrep of the name of the service (won’t work with, say, bind, whose daemon is named)
restart – this is the command to restart the service
status – this is the command to test the status

ordering – you need the package first
Apache example:
service { "httpd":
enable => true,
ensure => running,
hasrestart => true,
hasstatus => true,
(comma at end of last stanza is OK)

file { "host.conf":
path /etc/httpd/conf.d/$name,
mode => 0644,
owner => 'apache',
group => 'group',
content => "<VirtualHost *:80>
ServerName kermit.henson.local
DocumentRoot /var/www/html/kermit

Defining a new object uses lower case:
service["sshd"] {ensure => running }

Uppercase is for referencing existing objects:
require => Service["sshd"]
Be careful not to define an object twice (it will throw an error)

Every object has builtins to allow for logic involving other objects:
require – you can say the httpd service requires the package
before – you can say the package comes “before” the service
subscribe – the service is subscribed to the config file so if the config file
tag – attach a human word to your object

ensure => present

file { "host.conf":
path /etc/httpd/conf.d/$name,
mode => 0644,
owner => 'apache',
group => 'group',
content => "<VirtualHost *:80>
ServerName kermit.henson.local
DocumentRoot /var/www/html/kermit

require => Package['httpd']

puppet resource is a great way to translate your existing site into a manifest – a manifest is a collection of files that apply to the client.

puppet resource --types
prints out a list of types.

If you want to find out what a puppet manifest would look like on your current system, use puppet resource. Here’s an example of finding a puppet manifest for your swap mount:
puppet resource mount swap
mount { 'swap':
ensure => 'unmounted',
device => '/dev/vg00/swapvol',
dump => '0',
fstype => 'swap',
options => 'defaults',
pass => '0',
target => '/etc/fstab',

Facter runs Ruby code to generate a value for facts (61 facts available), here are sample facts:

You can extend this by adding additional facts. w00t!

can use as parameters
can use in conditionals
facts (e.g. $::processor0)
user defined ($myvar = “My Value”)

$::variable is a top-scope variable. More on scope later.

($x > $y)
($x < $y)
($x < $y) and !($x < $y)

can do arithmetic
$y >> 1 (why would you do this? but you can!)

case $myvar {
"My Value": {$content = "All is good" }
"": { $content = "bad var" }
default: { $content = $myvar }

case $::hostname {
/^ldap/: {include ldapserver}
/^www/: {include webserver}
default: {include base}

match =~ not match !~
if $::hostname =~ /^ldap/ { include ldapserver }
if $::hostname != /test/ { include production }

capture –
/^ldap(\d+)/ =~ {include "ldap$1"}

Ternary operator (C) (actually it’s more than ternary)
x == y ? "they are equal" : "they are different"

file {'httpd.conf':
  path => $operatingsystem ? {
    'Ubuntu' => '/etc/apache2/$name',
    'RedHat' => '/etc/httpd/conf/$name',
    default => '/usr/local/etc/httpd/conf/$name'
  content => "DocumentRoot /var/www/html"

in – Arrays

if $::hostname in ['www','web'] {
  service {'httpd':
    ensure => true

if $::kernelversion in ['2.6.35-22',''] {

BNF syntax of puppet grammar


ERB syntax
<% Ruby code %>
<%= Ruby expression %>
<%# comment %>
-%> – this puts no newline, so for example this will be all one line:
foo bar <% stuff here -%> baz bap
<%% escapes, so replace with <%
%%> escapes, so replace with %>
<%= @fact %>

<%= if @ipaddress_eth0 != "NONE" %>
ServerName <%= @printserver %>
<% end %>

iteration in templates

|domain| is variable expansion – replace with whatever the value of domain is

template example:
# resolv.conf build by Puppet
domain <%= domain %>
search <% searchdomains.each do |domain| -%><%= domain -%><% end -%><%= @domain %>
<% nameservers.each do |server| -%>
nameserver <%= server %>
<% end -%>

$nameservers = [ '', '', '' ]
$searchdomains = [ '', '', '' ]
file { "resolvconf":
path => "/etc/resolv.conf",
mode => 0644, owner=root, group=root,
content => template('resolvconf.erb')

content => template('header.erb','resolvconf.erb')

If you want to manage crontab, use the “cron” type
If you want to manage cron.d, or cron.hourly, use files.
Even though it’s called “cron” it depends on the provider, so you can use it on Mac OS X for example.

cron {'yum_cleaner':
command => "/usr/bin/yum clean all",
user => root,
hour => 2,
minute => 0

title – must be unique across “exec” (not across the whole manifest)
creates –