R.I.Pienaar
Puppet Camp Morristown 2016
Whirlwind Tour
of Puppet 4
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Who am I?
• Puppet user since 0.22.x
• Author Hiera, MCollective, facts.d, etc
• Blog at http://devco.net
• Tweets at @ripienaar
• Volcane on IRC
• Consultant for hire
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Puppet 4?
• Currently 4.5.2 and uses Semver.
• >10 releases and now ready for production
• Massive internal rewrite
• Formal language specification
• Many new and improved features
• New DSL, but backward(ish) compatible
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Language Types
class ntp (
$servers,
$config_dir = undef,
$ensure = “present”
) {
validate_array($servers)
validate_absolute_path($config_dir)
if !([$ensure in [“present”, “absent”]) {
fail(“not a valid value for ensure”)
}
}
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
class ntp (
Array[String] $servers,
Optional[Pattern[/^//]] $config_dir,
Enum[“present”, “absent”] $ensure = “present”
) {
# …
}
Language Types
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
type Ntp::Server = Pattern[
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-
zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-
z0-9-]*[A-Za-z0-9])$/
]
Language Types
class ntp (
Array[Ntp::Server] $servers,
Optional[Pattern[/^//]] $config_dir,
Enum[“present”, “absent”] $ensure = “present”
) {
# …
}
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$enable_real = $enable ? {
Boolean => $enable,
String => str2bool($enable),
Numeric => num2bool($enable),
default => fail('Illegal value for $enable
parameter'),
}
if 5 =~ Integer[1,10] {
notice("it's a number between 1 and 10")
}
Language Types
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Hash[String,
Struct[{
match => Optional[Enum["all", "any"]],
rules => Array[
Struct[{
fact => Optional[Data],
operator => Enum["==", "=~", ">", " =>",
"<", "<=", "has_ip_network"],
value => Data,
invert => Optional[Boolean]
}]
],
data => Optional[Hash[Pattern[/A[a-
z0-9_][a-zA-Z0-9_]*Z/], Data]],
classes => Optional[Array[Pattern[/A([a-z]
[a-z0-9_]*)?(::[a-z][a-z0-9_]*)*Z/]]]
}]
]
Language Types
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
type Classifier::Classification = Struct[{
match => Optional[Classifier::Matches],
rules => Array[Classifier::Rule],
data => Optional[Classifier::Data],
classes => Optional[Array[Classifier::Classname]]
}]
type Classifier::Classifications = Hash[
String, Classifier::Classification
]
Language Types
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$a={"a" => “b"}
$b={"c" => “d"}
notice($a+$b)
Native Data Merges
Notice: Scope(Class[main]): {a => b, c => d}
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$a=[1,2,3]
$b=[1,4,5,6]
notice(“sum: ${$a+$b}”)
notice(“difference: ${$a-$b}”)
Native Data Merges
sum: [1, 2, 3, 1, 4, 5, 6]
difference: [2, 3]
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
File {
mode => “0600”,
owner => “root”,
group => “root”
}
file{“/some/file”:
source => “puppet:///some/file”
}
include another_class
Resource Defaults
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
file {
default:
mode => “0600”,
owner => “root”,
group => “root”;
“/some/file”:
source => “puppet:///some/file”;
“/some/other/file”:
source => “puppet:///other/file”;
}
include another_class
Resource Defaults
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
[“puppet”, “facter”].each |$file| {
file{“/usr/bin/${file}”:
ensure => “link”,
target => “/opt/puppetlabs/bin/${file}”
}
}
Iteration
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$domains = [“example.com”, “example.net”]
$data = $domains.map |$domain| {
{
$domain => {
“relay” => “mx.${domain}”
}
}
}
Iteration
{
“example.com” => {“relay” => “mx.example.com”},
“example.net” => {“relay” => “mx.example.net”}
}
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
{
"example.net": {
"nexthop": "70.x.x.x",
"spamdestination": "rip@devco.net",
"spamthreshold": 1500,
"enable_antispam": 1
},
“example.com": {
"nexthop": "70.x.x.x",
"spamdestination": "rip@devco.net",
"spamthreshold": 1500,
"enable_antispam": 1
},
}
Iteration
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
each($domain) |$name, $domain| {
mail::domain{$name:
* => $domain
}
}
Iteration
Bonus feature: splat operator!
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$defaults = {
“spamthreshold” => 1500,
“enable_antispam” => 1
}
each($domain) |$name, $domain| {
mail::domain{$name:
* => $defaults + $domain
}
}
Iteration
Merging in the defaults and the supplied
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$defaults = {
“spamthreshold” => 1500,
“enable_antispam” => 1
}
each($domain) |$name, $domain| {
mail::domain{
default:
* => $defaults;
$name:
* => $domain;
}
}
Iteration
This is create_resources()
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
each($data) |$item| {
notice($item)
}
.syntax
$data.each |$item| {
notice($item)
}
Any function, including old ones
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
class myapp {
include myapp::install
include myapp::config
include myapp::service
notify{“done!”: }
}
Default Ordering
Recent Puppet 3 set ordering=manifest for same
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
% facter os
{
architecture => "x86_64",
distro => {
release => {
full => "7.2.1511",
major => "7",
minor => "2"
},
…
},
family => "RedHat",
hardware => "x86_64",
name => “CentOS",
…
}
Facts Hash
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
class bob {
notice($facts[“os”][“distro”][“release”])
}
Facts Hash
Notice I am not using $::facts ?
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
class bob {
$facts = “bad things will happen”
}
Facts Hash
Error while evaluating a '=' expression,
Attempt to assign to a reserved variable
name: 'facts'
It’s globally reserved along with a few others
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
:hierarchy:
- "%{facts.fqdn}"
- "location_%{facts.location}"
- "country_%{facts.country}"
- common
Facts Hash
Hiera 3 supports nested hashes too
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$ time facter
facter 0.05s user 0.04s system 69% cpu
0.126 total
Facter 3
It’s written in C++ but supports old facts
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$ facter os.distro.release.major
7
$ facter networking.interfaces.lo.bindings.0.address
127.0.0.1
Facter 3
The ‘0’ is for digging into arrays
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
$ puppet apply -e ‘$a = 1;$b = 2;notice($a+$b)’
Improved Parser
You can use a “;” to separate statements
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Improved Parser
Functions can go more or less anywhere
notify{hiera(“something”): }
notice("rand: ${fqdn_rand(100)}”)
notice("rand: ${10 + fqdn_rand(100)}")
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Improved Parser
Functions can go more or less anywhere
$pointless = hiera_hash(“things”)
$thing = $pointless[“thing”]
$thing = hiera_hash(“things”)[“thing”]
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Native Functions
function classifier::has_interface_detail (
String $match,
Enum["address",“network"] $what="address"
) {
$interfaces = $facts[“networking”][“interfaces”]
$ips = $interfaces.map |$iname, $interface| {
["bindings", "bindings6"].map |$bname| {
$interface[$bname].map |$binding| {
$binding[$what]
}
}
}.flatten
$match in $ips
}
c::has_interface_detail(“127.0.0.1”)
c::has_interface_detail(“::1”)
c::has_interface_detail(“127.0.0.0”, “network”)
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Native Functions
function classifier::fact_fetch (
String $keys
) {
$keys.split(/./).reduce($facts) |$memo, $key| {
if $memo !~ Undef {$memo[$key] }
}
}
classifier::facts_fetch(“os.distro.release.major”)
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Native Functions
function my::create_resource(
String $type,
Hash[String, Hash] $instances,
Hash $defaults = {}
) {
$instances.each |$r_name, $r_properties| {
Resource[$type] {$r_name:
* => $defaults + $r_properties
}
}
}
Thus a native DSL create_resources()
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Data in Modules
class ntp (
Array[Ntp::Server] $servers,
String $config_file
) {
}
# data/os/AIX.yaml
ntp::config_file: /etc/ntpd.conf
# data/common.yaml
ntp::config_file: /etc/ntp.conf
ntp::servers:
- 1.pool.ntp.org
…
Replaces params.pp pattern
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Lookup Strategies
class ntp (
Array[Ntp::Server] $servers,
String $config_file
) {
# …
}
# data/common.yaml
lookup_options:
ntp::servers:
merge:
strategy: unique
Replaces hiera_array() and hiera_hash()
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
AIO Packages
% rpm -qi puppet-agent
…
augeas 1.4.0
facter 3.1.8
hiera 3.2.0
marionette-collective 2.8.8
openssl 1.0.2h
puppet 4.5.2
puppet-ca-bundle 1.0.7
ruby 2.1.9
ruby-augeas 0.5.0
ruby-selinux 2.0.94
ruby-shadow 2.3.3
ruby-stomp 1.3.3
rubygem-deep-merge 1.0.1
rubygem-hocon 0.9.3
rubygem-net-ssh 2.9.2
rubygem-semantic_puppet 0.1.2
…
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
AIO Packages
% ls -l /etc/puppetlabs /opt/puppetlabs
/etc/puppetlabs:
total 8
drwxr-xr-x. 6 root root 4096 Mar 23 21:51 code
drwxr-xr-x. 4 root root 4096 Apr 15 10:25
mcollective
drwxr-xr-x. 3 root root 75 Apr 15 10:25 puppet
drwxr-xr-x 3 root root 20 Mar 23 21:37 pxp-agent
/opt/puppetlabs:
total 0
drwxr-xr-x. 2 root root 54 Apr 15 10:25 bin
drwxr-xr-x. 3 root root 20 Mar 23 21:37 facter
drwxr-xr-x 3 root root 20 Mar 23 21:37 mcollective
drwxr-xr-x. 9 root root 102 Apr 15 10:25 puppet
drwxr-xr-x 4 root root 32 Mar 23 21:54 pxp-agent
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Resources
• This talk in blog form http://srt.ly/jj
• Iterating in Puppet http://srt.ly/jk
• Data in Modules http://srt.ly/jg
• Lookup Strategies http://srt.ly/jl
• Resource Wrapper Pattern http://srt.ly/jm
• Params.pp in Puppet 4 http://srt.ly/jn
• The Lookup Function http://srt.ly/jo
• Type Aliases http://srt.ly/jp
• A module showing off modern Puppet http://srt.ly/jq
R.I.Pienaar | rip@devco.net | http://devco.net | @ripienaar
Questions?
twitter: @ripienaar
email: rip@devco.net
blog: www.devco.net
github: ripienaar
freenode: Volcane
slack: ripienaar
http://devco.net/

Whirlwind Tour of Puppet 4

  • 1.
    R.I.Pienaar Puppet Camp Morristown2016 Whirlwind Tour of Puppet 4
  • 2.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Who am I? • Puppet user since 0.22.x • Author Hiera, MCollective, facts.d, etc • Blog at http://devco.net • Tweets at @ripienaar • Volcane on IRC • Consultant for hire
  • 3.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Puppet 4? • Currently 4.5.2 and uses Semver. • >10 releases and now ready for production • Massive internal rewrite • Formal language specification • Many new and improved features • New DSL, but backward(ish) compatible
  • 4.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Language Types class ntp ( $servers, $config_dir = undef, $ensure = “present” ) { validate_array($servers) validate_absolute_path($config_dir) if !([$ensure in [“present”, “absent”]) { fail(“not a valid value for ensure”) } }
  • 5.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar class ntp ( Array[String] $servers, Optional[Pattern[/^//]] $config_dir, Enum[“present”, “absent”] $ensure = “present” ) { # … } Language Types
  • 6.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar type Ntp::Server = Pattern[ /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a- zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za- z0-9-]*[A-Za-z0-9])$/ ] Language Types class ntp ( Array[Ntp::Server] $servers, Optional[Pattern[/^//]] $config_dir, Enum[“present”, “absent”] $ensure = “present” ) { # … }
  • 7.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $enable_real = $enable ? { Boolean => $enable, String => str2bool($enable), Numeric => num2bool($enable), default => fail('Illegal value for $enable parameter'), } if 5 =~ Integer[1,10] { notice("it's a number between 1 and 10") } Language Types
  • 8.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Hash[String, Struct[{ match => Optional[Enum["all", "any"]], rules => Array[ Struct[{ fact => Optional[Data], operator => Enum["==", "=~", ">", " =>", "<", "<=", "has_ip_network"], value => Data, invert => Optional[Boolean] }] ], data => Optional[Hash[Pattern[/A[a- z0-9_][a-zA-Z0-9_]*Z/], Data]], classes => Optional[Array[Pattern[/A([a-z] [a-z0-9_]*)?(::[a-z][a-z0-9_]*)*Z/]]] }] ] Language Types
  • 9.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar type Classifier::Classification = Struct[{ match => Optional[Classifier::Matches], rules => Array[Classifier::Rule], data => Optional[Classifier::Data], classes => Optional[Array[Classifier::Classname]] }] type Classifier::Classifications = Hash[ String, Classifier::Classification ] Language Types
  • 10.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $a={"a" => “b"} $b={"c" => “d"} notice($a+$b) Native Data Merges Notice: Scope(Class[main]): {a => b, c => d}
  • 11.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $a=[1,2,3] $b=[1,4,5,6] notice(“sum: ${$a+$b}”) notice(“difference: ${$a-$b}”) Native Data Merges sum: [1, 2, 3, 1, 4, 5, 6] difference: [2, 3]
  • 12.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar File { mode => “0600”, owner => “root”, group => “root” } file{“/some/file”: source => “puppet:///some/file” } include another_class Resource Defaults
  • 13.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar file { default: mode => “0600”, owner => “root”, group => “root”; “/some/file”: source => “puppet:///some/file”; “/some/other/file”: source => “puppet:///other/file”; } include another_class Resource Defaults
  • 14.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar [“puppet”, “facter”].each |$file| { file{“/usr/bin/${file}”: ensure => “link”, target => “/opt/puppetlabs/bin/${file}” } } Iteration
  • 15.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $domains = [“example.com”, “example.net”] $data = $domains.map |$domain| { { $domain => { “relay” => “mx.${domain}” } } } Iteration { “example.com” => {“relay” => “mx.example.com”}, “example.net” => {“relay” => “mx.example.net”} }
  • 16.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar { "example.net": { "nexthop": "70.x.x.x", "spamdestination": "rip@devco.net", "spamthreshold": 1500, "enable_antispam": 1 }, “example.com": { "nexthop": "70.x.x.x", "spamdestination": "rip@devco.net", "spamthreshold": 1500, "enable_antispam": 1 }, } Iteration
  • 17.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar each($domain) |$name, $domain| { mail::domain{$name: * => $domain } } Iteration Bonus feature: splat operator!
  • 18.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $defaults = { “spamthreshold” => 1500, “enable_antispam” => 1 } each($domain) |$name, $domain| { mail::domain{$name: * => $defaults + $domain } } Iteration Merging in the defaults and the supplied
  • 19.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $defaults = { “spamthreshold” => 1500, “enable_antispam” => 1 } each($domain) |$name, $domain| { mail::domain{ default: * => $defaults; $name: * => $domain; } } Iteration This is create_resources()
  • 20.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar each($data) |$item| { notice($item) } .syntax $data.each |$item| { notice($item) } Any function, including old ones
  • 21.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar class myapp { include myapp::install include myapp::config include myapp::service notify{“done!”: } } Default Ordering Recent Puppet 3 set ordering=manifest for same
  • 22.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar % facter os { architecture => "x86_64", distro => { release => { full => "7.2.1511", major => "7", minor => "2" }, … }, family => "RedHat", hardware => "x86_64", name => “CentOS", … } Facts Hash
  • 23.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar class bob { notice($facts[“os”][“distro”][“release”]) } Facts Hash Notice I am not using $::facts ?
  • 24.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar class bob { $facts = “bad things will happen” } Facts Hash Error while evaluating a '=' expression, Attempt to assign to a reserved variable name: 'facts' It’s globally reserved along with a few others
  • 25.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar :hierarchy: - "%{facts.fqdn}" - "location_%{facts.location}" - "country_%{facts.country}" - common Facts Hash Hiera 3 supports nested hashes too
  • 26.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $ time facter facter 0.05s user 0.04s system 69% cpu 0.126 total Facter 3 It’s written in C++ but supports old facts
  • 27.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $ facter os.distro.release.major 7 $ facter networking.interfaces.lo.bindings.0.address 127.0.0.1 Facter 3 The ‘0’ is for digging into arrays
  • 28.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar $ puppet apply -e ‘$a = 1;$b = 2;notice($a+$b)’ Improved Parser You can use a “;” to separate statements
  • 29.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Improved Parser Functions can go more or less anywhere notify{hiera(“something”): } notice("rand: ${fqdn_rand(100)}”) notice("rand: ${10 + fqdn_rand(100)}")
  • 30.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Improved Parser Functions can go more or less anywhere $pointless = hiera_hash(“things”) $thing = $pointless[“thing”] $thing = hiera_hash(“things”)[“thing”]
  • 31.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Native Functions function classifier::has_interface_detail ( String $match, Enum["address",“network"] $what="address" ) { $interfaces = $facts[“networking”][“interfaces”] $ips = $interfaces.map |$iname, $interface| { ["bindings", "bindings6"].map |$bname| { $interface[$bname].map |$binding| { $binding[$what] } } }.flatten $match in $ips } c::has_interface_detail(“127.0.0.1”) c::has_interface_detail(“::1”) c::has_interface_detail(“127.0.0.0”, “network”)
  • 32.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Native Functions function classifier::fact_fetch ( String $keys ) { $keys.split(/./).reduce($facts) |$memo, $key| { if $memo !~ Undef {$memo[$key] } } } classifier::facts_fetch(“os.distro.release.major”)
  • 33.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Native Functions function my::create_resource( String $type, Hash[String, Hash] $instances, Hash $defaults = {} ) { $instances.each |$r_name, $r_properties| { Resource[$type] {$r_name: * => $defaults + $r_properties } } } Thus a native DSL create_resources()
  • 34.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Data in Modules class ntp ( Array[Ntp::Server] $servers, String $config_file ) { } # data/os/AIX.yaml ntp::config_file: /etc/ntpd.conf # data/common.yaml ntp::config_file: /etc/ntp.conf ntp::servers: - 1.pool.ntp.org … Replaces params.pp pattern
  • 35.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Lookup Strategies class ntp ( Array[Ntp::Server] $servers, String $config_file ) { # … } # data/common.yaml lookup_options: ntp::servers: merge: strategy: unique Replaces hiera_array() and hiera_hash()
  • 36.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar AIO Packages % rpm -qi puppet-agent … augeas 1.4.0 facter 3.1.8 hiera 3.2.0 marionette-collective 2.8.8 openssl 1.0.2h puppet 4.5.2 puppet-ca-bundle 1.0.7 ruby 2.1.9 ruby-augeas 0.5.0 ruby-selinux 2.0.94 ruby-shadow 2.3.3 ruby-stomp 1.3.3 rubygem-deep-merge 1.0.1 rubygem-hocon 0.9.3 rubygem-net-ssh 2.9.2 rubygem-semantic_puppet 0.1.2 …
  • 37.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar AIO Packages % ls -l /etc/puppetlabs /opt/puppetlabs /etc/puppetlabs: total 8 drwxr-xr-x. 6 root root 4096 Mar 23 21:51 code drwxr-xr-x. 4 root root 4096 Apr 15 10:25 mcollective drwxr-xr-x. 3 root root 75 Apr 15 10:25 puppet drwxr-xr-x 3 root root 20 Mar 23 21:37 pxp-agent /opt/puppetlabs: total 0 drwxr-xr-x. 2 root root 54 Apr 15 10:25 bin drwxr-xr-x. 3 root root 20 Mar 23 21:37 facter drwxr-xr-x 3 root root 20 Mar 23 21:37 mcollective drwxr-xr-x. 9 root root 102 Apr 15 10:25 puppet drwxr-xr-x 4 root root 32 Mar 23 21:54 pxp-agent
  • 38.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Resources • This talk in blog form http://srt.ly/jj • Iterating in Puppet http://srt.ly/jk • Data in Modules http://srt.ly/jg • Lookup Strategies http://srt.ly/jl • Resource Wrapper Pattern http://srt.ly/jm • Params.pp in Puppet 4 http://srt.ly/jn • The Lookup Function http://srt.ly/jo • Type Aliases http://srt.ly/jp • A module showing off modern Puppet http://srt.ly/jq
  • 39.
    R.I.Pienaar | rip@devco.net| http://devco.net | @ripienaar Questions? twitter: @ripienaar email: rip@devco.net blog: www.devco.net github: ripienaar freenode: Volcane slack: ripienaar http://devco.net/