By - scmGalaxy.com
Lab Environment
Pluralsight Courses
Course Content
This course is the first of a series of courses where we help you to learn the new skills that are needed with Puppet 4 and Puppet Enterprise. The leading software in Configuration Management.
Puppet 4: Language Essentials
Puppet 4: Working with Modules and Classes
Puppet 4: Working with Files and Templates
Puppet 4: Hiera the Single Source of Truth
Puppet 4: Server and Puppet Enterprise
Puppet Language and "Idempotency"
Next up : Installing the Puppet Client
Most distributions still ship Puppet 3 in their internal repositories. To be able to make use of the latest Puppet 4 code then we must first setup software repositories pointing to Puppetlabs.
sudo rpm -Uvh \
https://yum.puppetlabs.com/puppetlabs-release-pc1-el-
7.noarch.rpm
Puppet collections are new to Puppet 4 and ensure that all dependency packages such as heira facter and ruby come from the same repository ensure most compatibility with the client.
wget https://apt.puppetlabs.com/puppetlabs-release-pc1-trusty.deb
sudo dpkg -i puppetlabs-release-pc1-trusty.deb
sudo apt-get update
The process for Ubuntu is similar. Here we use the code name of "trusty" for the 14e04 repository.
sudo yum install puppet-agent #CentOS
sudo apt-get install puppet-agent #Ubuntu
On both the Ubuntu and CentOS systems we can now install the All In One Client. To ensure that the package will not clash with the standard repositories the package name is puppet-agent
puppet agent -- version
puppet config print
puppet config print confdir
puppet config print certname
puppet config print { config rundir ssldir runintrerval}
Next up : Working with Puppet Apply and Manifests
notify {'Hello World' : }
The notify resource type can be used to send a message to the agent log or screen if being used interactively. This makes a great and simple first manifest.
sudo puppet parser validate heloworld.pp
Although this manifest is very simple we should get in the habit of validating code as we create it. Helping to keep our code robust and healthy.
sudo puppet apply helloworld.pp
sudo puppet apply -e "notify { 'Hello World' : }"
sudo puppet resource service puppet
service { 'puppet':
ensure => 'stopped',
enable => false, }
sudo puppet parser validate puppet-service.pp
sudo puppet apply puppet-service.pp
We currently do want to use the Puppet agent. This can run as a daemon service so we should check that it is both stopped and disabled.
service { 'puppet':
ensure => 'stopped',
enable => false,
}
Ensure the Puppet Agent is not running Most languages have their recommend style guide. The way the code should be laid out. The full guide is in the documentation:
https://docs.puppet.com/guides/style_guide.html
sudo puppet module install theurbanpenguin/puppet_vim
include puppet_vim
sudo puppet parser validate puppet-vim.pp
sudo puppet apply puppet-vim.pp
notify { 'Hello World': }
Validate
sudo puppet parser validate test.pp
sudo puppet apply test.pp
Apply Code
sudo puppet apply -e \
"notify { 'Message': }"
Install a module
sudo puppet module install \ theurbanpenguin/puppet_vim
Apply
sudo puppet apply -e \
"include puppet_vim"
type { 'title':
attribute => value,
}
When defining a resource we first set the resource type. This should be all in lower case. We then set a title and key value pairs. The title and key value pairs are enclosed within brace brackets.
sudo puppet describe --list
sudo puppet describe notify
sudo puppet describe user
sudo puppet describe user --short
If you want to read the documentation on a resource type we can use describe it!
sudo puppet describe file | grep namevar
file { '/etc/motd': ensure => 'file',
content => 'Welcome to my server',
}
file { 'Message File':
ensure
An attribute may be marked as the namevar for that resource type. We can then use the resource title to populate the designated attribute so long as the attribute is not independently set.
package { 'ntp':
ensure => 'installed', #'absent','purged','latest','4.1'
#name => 'ntp', #not used here as we make use of title
provider => 'yum', #Normally not required
}
A resource package allows us to install or remove a package. We do not normally need to set many parameters. The package name can make use of the title.
service { 'ntpd':
ensure => 'running', #'stopped',
#name => 'ntp', #Useful where the service name differs,
enable => true, #false
}
$ntp_conf = '#Managed by Puppet
server 192.168.0.3 iburst
driftfile /var/lib/ntp/drift'
file { '/etc/ntp.conf':
ensure => 'file'
content => $ntp_conf, }
user { 'bob':
ensure => 'present',
managehome => true,
groups => [ 'sudo', 'users'],
password => pw_hash('Password1','SHA-512','salt'),
We can add or remove users with Puppet. The password we set should be encrypted. For this we can use the pw_hash function from the standard library module. This will need to be installed if not already. The group membership is multi valued so we use an array.
group { 'admins': }
host { 'timeserver' :
ip => '192.168.0.3',
host_aliases => 'tock',
}
ssh_authorized_key {'tux@centos7':
user => 'tux',
type => 'ssh-rsa',
Key => 'sdjk jsdls kdslkj dlslkjd',
}
puppet describe --list
List all resources
puppet describe file
List documentation for the file resource.
file { '/etc/motd':
ensure => 'file',
content => 'My message',
}
Making use of the path parameter via namevar and the title.
file { 'Message':
ensure => 'file',
content => 'My message',
path => '/etc/motd',
}
We can use the path attribute independent of the title.
package { 'ntp':
ensure => 'installed',
}
Installing a package
service {'ntpd' :
ensure => 'running',
enable => true,
}
Ensure a service is running and enabled to start on system boot.
sudo puppet module \
install puppetlabs\stdlib
Installing the standard library gives us access to functions such as pw_hash.
File {
owner => ‘root’,
mode => ‘0600’,
}
Where we need consistent settings for many resources we can use resource defaults.
$message = 'The message' #String
$ntp_service ='ntp' #String
$size = 100 #Number
$answer = false #Boolean
Variables are denoted with the $. In Puppet 4 the variable name must start with a lower case letter or underscore. They can contain lower case letters, numbers and underscores.
$admingroups = ['wheel','adm']
$user ={
'username' => 'bob',
'uid' => '2011',
}
A hashed array contain key pairs, note that the key ¡s quoted unlike a resource.
service {'NTP_Service':
ensure => 'running',
enable => true,
name => $ntp_service,
}
notify {"The $ {ntp_service} is up and running":}
If a variable is used on its own then we do not need to delimit it. Whereas if it is used within a string we need to delimit the variable with brace brackets. Use double quotes to interpolate the variable.
$admingroups = ['wheel','adm']
notify {"The first group is ${admingroups[0]}":}
$user = { 'username' => 'bob', 'userid' => '2011',}
notify {"The user's name is ${user['username']}":}
To access a single value from and array we can use the index or key.
$ntp_conf = @(END)
driftfile /var/lib/ntp/drift
server tock prefer iburst
server uk.pool.ntp.org
END
To create long multi line string we can use the heredoc. The tag END can be anything but must be consistent at the start and the end of the string.
if $facts['os']['family'] == ‘RedHat’ {
notify { ‘Red Hat’: }
} #Modern Fact
if $facts[‘osfamily’] == ‘RedHat’ {
notify { ‘Red Hat’: }
} #Legacy Fact
String comparisons are case insensitive unless we use regular expression matches. Some nested facts are still available as legacy facts at the top level.
if $facts[‘os’][‘family’] == ‘RedHat’ {
notify { ‘Red Hat’: }
}
else {
notify { ‘Debian’: }
}
If we only have two conditions then we can use if and else
if $facts[‘os’][‘family’] == ‘RedHat’ {
notify { ‘Red Hat’: }
elsif $facts[‘os’][‘family’] == ‘Debian’ {
notify { ‘Debian’: }
}
else {
fail (“Your OS, ${facts[‘os’][‘family’]}, is untested”)
}
More options can be coped with using elsif. The fail function can be useful to exit the manifest in the event of untested options being returned.
if $facts[‘os’][‘family’] != ‘RedHat’ {
notify { ‘Debian’: }
}
unless $facts[‘os’][‘family’] == ‘RedHat’ {
notify { ‘Debian’: }
}
Rather than use not equals to, !=, the use of unless may make the code more readable. New in Puppet 4 we can also use the else statement with unless
case $facts[‘os’][‘family’] {
‘RedHat’: { notify {‘This is Red Hat based’: }}
‘Debian’ : { notify {‘This is Debian based’: }}
default : { fail( ‘Your OS is untested')}
}
The default option acts a little like the else option within if statements.
$ntp_service = $facts[‘os’][‘family’] ? {
‘RedHat’ => ‘ntpd’,
‘Debian’ => ‘ntp’,
}
A selector can be used to populate a variable based on a condition. These are great where we need to set just one variable but in our case we need to see both the admin group and service.
$facts ['os'] ['family'] =~ /Redhat/
$facts ['os'] ['family'] =~ /^Redhat$/
$facts ['networking'] ['fqdn'] =~ /^www\d/
$facts ['networking'] ['fqdn'] =~ /\.example\.com$/
Where we want case sensitive matching or more flexibility we can make use of regular expression matching. Instead of quoting the string we use forward slashes. Note the period has special meaning within a regex so we escape them in the FQDN.
facter partitions
New in Puppet 4 are lamdas and iterations. First we will look at a group of partitions that we can iterate through.
each ( $facts[‘partitions’] ) | $devname, $devprops | {
if $devprops[‘mount’] {
notify { “Device: ${devname} is ${devprops[‘size’]}”: }
}
}
We can iterate though a collection with each. The values returned populate lambda denoted with at least one variable name between the pipe symbols.
$variable1 = 'fred'
$variable2 = 23
$variable3 = true
Scalar Variables
$variablearray = ['fred', 23, true]
$variablehash = {
'name' => 'fred',
'age' => 23,
'employee => true,
}
Arrays
$message = "Your name is ${variable1}"
Interpolation
facter
puppet facts
Facts
$facts ['os'] ['family']
Modern Facts
$facts['osfamily']
Legacy Facts
==
!=
=~
Conditional
if $facts ['os']['family'] == 'redhat' {
..}
elseif $facts ['os'] ['family'] == 'debian' {...}
else {...}
if
case $facts ['os'] ['family']{
'redhat' : {...}
'debian' : {...}
default : {...}
}
Case
ntp_service = $facts ['os'] ['family'] ? {
'redhat' => 'ntpd',
'debian' => 'ntp',
}
Selector
each ($facts['partitions']) | $n, $a | {
notify {"Device ${n} is ${a['size']}":
}
}
Iteration and Lambdas
If we don’t add relationships to our resources then ordering of those resources will be down to the agent version:
Manifest : The default in Puppet 4. Resources will be ordered in the same order that they are read from the manifest.
Title-Hash : The default in all previous Puppet versions to Puppet 4. Randomized on the resource title but consistent between runs.
Random : Useful to flush out resources that have not been explicitly ordered.
Package: Typically we want the package resource first.
File: We want the file resource next. This will add the configuration data for the service. We want this after the Package to prevent the Package from overwriting our custom file.
Service: Finally we want the service resource to start the service. We also need the service to restart on the file change. This restart cannot be managed automatically without explicit relationship assignment.
package { ‘pkg1’: }
file { ‘file1’: }
service { ‘service1’: }
Other than the issue in restarting the service the manifest ordering would appear on first view to be ok. If pkg1 fails to install the other resources will fail based on the dependency not being installed.
package { ‘pkg1’: }
file { ‘file1’: }
service {‘service1’: }
package {'pkg2':}
file { 'file2':}
service {'service2':}
Now if pkg1 fails then so will all the other resources including those relating to pkg2 which perhaps do not actually require pkg1.
package { ‘pkg1’: before => File[‘file1’], }
file { ‘file1’: before => Service[‘service1’],}
service { ‘service1’: }
package { ‘pkg2’: before => File[‘file2’], }
file { ‘file2’: before => Service[‘service2’], }
service { ‘service2’: }
sudo puppet apply --ordering=random ntp1.pp
Not every resource will need a relationship defined. Some resources such as the /etc/motd file will be perfect without any dependencies. However resources that do have dependencies that have not been explicitly defined can be detected using the random option.
file { ‘/etc/motd’: } # Define the file
service { ‘ntpd’:
require => File[‘/etc/ntpd’], # Reference the file
}
We need to build a new airport # Define the airport
It will be London Airport #Reference the airport
As you know we use proper case when making reference to a resource type and lower case when it is defined, If you find this confusing then compare it to English and Proper Nouns.
sudo puppet describe user | grep -A5 Autorequires
Many resources define parameters with an autorequire. For example if a user and group are in the same manifest and the user needs to belong to the group the group is processed first.
package { ‘pkg1’: before => File[‘file1’], }
file { ‘file1’: notify => Service[‘service1’],}
service { ‘service1’: }
package ['ntp'] -> File ['/etc/ntp.conf'] ~> Service ['NTP_S']
Yes we can but the Style Guide does advise against it from a readability standpoint. An additional line is added that defines the relationships.