Puppet: audit on external file changes? - puppet

In my puppet managed environment, one service needs to be restarted each time a file changes. The file itself is not under puppet's control but part of a package, i.e., may gets updated during (yum) updates.
Thus, I would like to 'subscribe' to this file and trigger a service restart on changes.
Since the file is not managed by Puppet, some audit-like approach does not work
file { '/path/to/foo':
audit => content,
}
notify { 'foo.notify':
subscribe => File['/path/to/foo'],
message => "foo has changed"
}
Is there a way, to realize something like that in Puppet?
Ideally, I would only need to compare on the client the current vs the previous file hash.
But AFAIS within Puppet's structure, it would require to keep the current file hash on the puppet master and compare it again on the client (which may work via a fact but would be clumsy(?)).

It does seem to work with Puppet 4.4.2 and 3.4.3, so it's safe to assume that both Puppet 3.x and 4.x do the right thing here:
$ cd /tmp
$ echo foo >foo
$ puppet apply -e 'file { "/tmp/foo": audit => "content" } ~> exec { "/bin/echo DING": refreshonly => true }'
Notice: Compiled catalog for this-box in environment production in 0.13 seconds
Notice: /Stage[main]/Main/File[/tmp/foo]/content: audit change: newly-recorded value {md5}d3b07384d113edec49eaa6238ad5ff00
Notice: Applied catalog in 0.07 seconds
$ puppet apply -e 'file { "/tmp/foo": audit => "content" } ~> exec { "/bin/echo DING": refreshonly => true }'
Notice: Compiled catalog for this-box in environment production in 0.12 seconds
Notice: Applied catalog in 0.06 seconds
$ echo bar >foo
$ puppet apply -e 'file { "/tmp/foo": audit => "content" } ~> exec { "/bin/echo DING": refreshonly => true }'
Notice: Compiled catalog for this-box in environment production in 0.12 seconds
Notice: /Stage[main]/Main/File[/tmp/foo]/content: audit change: previously recorded value {md5}d3b07384d113edec49eaa6238ad5ff00 has been changed to {md5}c157a79031e1c40f85931829bc5fc552
Notice: /Stage[main]/Main/Exec[/bin/echo DING]: Triggered 'refresh' from 1 events
Notice: Applied catalog in 0.09 seconds
Please note that notify does not work very well with events. Putting the subscribe in an actual service resource should work much better and lead to a service restart.

Related

Puppet agent can't be deployed module from master

I'm just start learning Puppet, really new to this world. I'm using puppet 2.7.26 on my two learning VMs --
puppet-master 192.168.160.131
eisen-suse11 192.168.160.129
Follow the turial, I've signed the node "eisen-suse11" to puppet-master successfully--
puppet-master:/etc/puppet/modules/motd/manifests # puppet cert --list --all
+ "eisen-suse11" (A0:7F:E2:77:30:9A:96:E3:79:FD:F7:1E:59:35:5B:1E)
+ "puppet-master" (38:90:B5:8A:68:8A:A7:44:8A:2F:07:D3:F3:AC:E8:80) (alt names: "DNS:puppet", "DNS:puppet-master", "DNS:puppet-master.suse11", "DNS:puppet.suse11")
+ "puppet-master.suse11" (5D:9E:A4:D9:0C:5F:69:07:FA:55:13:C3:38:6D:9B:26)
Then follow the book, I write a module -- motd -- which should put a file to client node --
puppet-master:/etc/puppet/modules/motd/manifests # cat init.pp
class motd{
package{ 'setup':
ensure => present,
}
file{ '/etc/motd':
ensure => present,
owner => 'root',
group => 'root',
mode => '0644',
source => "puppet://$puppetserver/modules/motd/etc/motd",
require => Package['setup']
}
}
puppet-master:/etc/puppet/modules/motd/manifests # cat site.pp
$puppetserver='puppet-master.suse11'
node 'eisen-suse11'{
include motd
}
But when I tested "puppet agent --test --trace" on the client node -- eisen-suse11 --- it's all quiet --
eisen-suse11:~ # puppet agent --test --trace
info: Caching catalog for eisen-suse11
info: Applying configuration version '1633779962'
notice: Finished catalog run in 0.01 seconds
eisen-suse11:~ # ls /etc/motd
ls: cannot access /etc/motd: No such file or directory
That "/etc/motd" is not copied from puppet-master --
Does anyone can help? Any idea would be appreciated.
RGS
Eisen
The problem is that your node is receiving an empty catalog, which is happening because you put your site.pp file in the wrong place. Puppet will not find it inside the module. It has been a very long time since I wrote code for Puppet 2 (and I hung on to that version much longer than was healthy), but as I recall, the correct directory for that file would be /etc/puppet/manifests.
But again, as I wrote in comments, Puppet 2 is utterly obsolete and well past the end of its life. Ditch it, and also ditch any books you have that teach it. The only reason I can think of to learn this version of Puppet is that you have an existing legacy infrastructure that you are obligated to maintain, but if you are faced with such a Puppet code base in 2021 then it would be best to rewrite from scratch for Puppet 7.

Checking if a directory exists before copying in Puppet

I am trying to get a backup of a subdirectory before deleting the parent directory by copying the subdirectory into a different location.
This is how I have done this:
exec { "install_path_exists":
command => "/bin/true",
onlyif => "/usr/bin/test -d ${install_path}",
path => ['/usr/bin','/usr/sbin','/bin','/sbin'],
}
file { "server_backup_dir" :
ensure => 'directory',
path => "${distribution_path}/backup/server",
recurse => true,
source => "file:///${install_path}/repository/deployment/server",
require => Exec["install_path_exists"],
}
Exec checks if the directory exists, and returns true if so. The "server_backup_dir" file resource requires the "install_path_exists" exec to return true if the directory exists.
When the directory does not exist, and "install_path_exists" returns false, "server_backup_dir" executes anyway, and produces the following error.
Error: /Stage[main]/Is/File[server_backup_dir]: Could not evaluate:
Could not retrieve information from environment production source(s)
file:////usr/local/{project_location}/repository/deployment/server
What is wrong with my approach, and how can I fix this? Thanks in advance.
I'll break this up into two parts, what is wrong, and how to fix it.
What is wrong with my approach ...
You are misunderstanding the 'require' line and the nature of relationships in Puppet, and also how Puppet uses the return code of the command executed in an Exec.
When you use any of the four so-called metaparameters for relationships in Puppet - those being: require, before, subscribe & notify - you tell Puppet that you want the application of one resource to be ordered in time in relation to another. (Additionally, the 'subscribe' and 'notify' respond to refresh events but that's not relevant here.)
So, when Puppet applies a catalog built from your code, it will firstly apply the Exec resource, i.e. execute the /bin/true command, if and only if the install path exists; and then it will secondly manage the server_backup_dir File resource. Note also that it will apply the File resource irrespective of whether the Exec command actually was executed; the only guarantee being that /bin/true will never be run after the File resource.
Furthermore, the return code of the command in the Exec functions differently to what you're expecting. An exit status of 0 as the /bin/true command returns only tells Puppet to allow configuration to continue; compare that to an Exec command returning a non-zero exit status, which would cause Puppet to halt execution with an error.
Here's a simple demonstration of that:
▶ puppet apply -e "exec { '/usr/bin/false': }"
Notice: Compiled catalog for alexs-macbook-pro.local in environment production in 0.08 seconds
Error: '/usr/bin/false' returned 1 instead of one of [0]
Error: /Stage[main]/Main/Exec[/usr/bin/false]/returns: change from 'notrun' to ['0'] failed: '/usr/bin/false' returned 1 instead of one of [0]
Notice: Applied catalog in 0.02 seconds
For more info, read the page I linked above carefully. It generally takes a bit of time to get your head around relationships and ordering in Puppet.
how can I fix this?
You would normally use a custom fact like this:
# install_path.rb
Facter.add('install_path') do
setcode do
Facter::Core::Execution.execute('/usr/bin/test -d /my/install/path')
end
end
And then in your manifests:
if $facts['install_path'] {
file { "server_backup_dir" :
ensure => 'directory',
path => "${distribution_path}/backup/server",
recurse => true,
source => "file:///my/install/path/repository/deployment/server",
}
}
Consults docs for more info on writing and including custom facts in your code base.
Note:
I notice at the end that you reuse $install_path in the source parameter. If your requirement is to have a map of install paths to distribution paths, you can also build a structured fact. Without knowing exactly what you're trying to do, however, I can't be sure how you would write that piece.

How to stop Puppet applying a configuration when there is an error?

I am currently hitting, for me, somewhat unintuitive behaviour in Puppet - most likely because I don't completely understand the Puppet ethos yet.
OK I have a simple puppetsimple.sh running in the puppet agent which is applying configurations from puppet master. This is all running smoothly and as expected.
Unintuitive (for me) However when I, as part of setting up master, create an error, and then run puppetsimple.sh in the agent, it will strike the error, notify me of it, and continue to apply all the other changes for that configuration.
This effectively leaves the agent in a broken state, because it pushes ahead even when there is an error.
Is there a setting somewhere to say "hey, if you strike an error, stop, revert to how you were, and carry on your merry way"?
Given the example below. I am intentionally enabling an invalid conf file (.confX) - I get notified of the error, but it continues to populate "index.html" with "Hello World 3".
define a2ensite {
exec { 'a2ensite':
path => [ '/bin', '/usr/bin', '/usr/sbin' ],
command => "a2ensite ${title}",
notify => Service['apache2'],
}
}
class mysite {
include apache
file { '/etc/apache2/sites-available/mysite.example.org.conf':
owner => root,
group => root,
mode => 0644,
source => "puppet:///files/mysite/mysite_apache.conf",
notify => Service['apache2'],
}
a2ensite { 'mysite.example.org.confX': }
file { ['/home/', '/home/www/', '/home/www/mysite.example.org']:
ensure => directory,
owner => root,
group => root,
mode => 0755,
}
file { '/home/www/mysite.example.org/index.html':
owner => www-data,
group => www-data,
mode => 755,
content => "Hello World 3",
}
}
If one reosurce failing means that another resource should not be modified, then that is a dependency relationship that you need to model via require. A failing dependency will cause puppet to skip those resources.
But, in general, puppet does not stop or rollback runs when it hits an error. If you need to rollback, it is on you to either revert to an older puppet configuration or use some other capability to revert the node.

Running a command once after a group of packages is installed

I have an existing puppet manifest which installs a bunch of php5 packages and only after being installed restarts apache. The simplified manifest is something like
package { 'apache-php':
name => $modules,
ensure => installed
}
exec {'enable-mod-php':
command => $enable_cmd,
refreshonly => true
}
Package['apache-php'] ~> Exec['enable-mod-php'] ~> Service['apache']
After a system upgrade catalog runs have started failing with the following error message:
Error: Failed to apply catalog: Parameter name failed on Package[apache-php]: Name must be a String not Array at /etc/puppet/modules/apache/manifests/php.pp:22
I found out that I was using an undocumented feature/bug: Puppet 3.4.0 name as an array in package.
However, I'm having a hard time finding out how to redo my setup after the upgrade. How can I rewrite this manifest so that it works with more recent puppet versions?
Instead of using an arbitrary title for the package define in your example. (eg. apache-php) and using a name parameter, you can do the following:
$modules = ['foo','bar','baz']
package { $modules:
ensure => present
notify => Exec['enable-mod-php']
}
exec {'enable-mod-php':
command => $enable_cmd,
refreshonly => true,
notify => Service['apache']
}
service { 'apache':
# your apache params
}
I haven't looked at the code for the package provider, but can verify that the above works. You should also note that chaining arrows are all well and good, but according to the Puppet style guide, metaparameters are preferred.
Hope this helps.

Puppet ignores my node.pp entry

My Puppet master and agent are on the same machine. The master node.pp file contains this:
node 'pear.myserver.com' {
include ntp
}
The ntp.pp file contains this:
class ntp {
package { "ntp":
ensure => installed
}
service { "ntp":
ensure => running,
}
}
The /etc/hosts file contains the line:
96.124.119.41 pear.myserver.com pear
I was able to successfully launch puppetmaster, but when I execute this, ntp doesn't get installed (it is not installed already, I checked).
puppet agent --test --server='pear.myserver.com'
It just reports this:
info: Caching catalog for pear.myserver.com
info: Applying configuration version '1387782253'
notice: Finished catalog run in 0.01 seconds
I don't know what else I could have missed. Can you please help? Note that I replaced the actual server name with 'myserver' for security reasons.
I was following this tutorial: http://bitfieldconsulting.com/puppet-tutorial
$puppet agent --test
This will fetch compiled catalog from Master puppet, which is in /etc/puppetlabs/puppet/manifests/site.pp and run locally.
$puppet apply /etc/puppet/modules/ntp/manifests/ntp.pp
Will apply locally

Resources