How to use multiple onlyif in Puppet Augeas resources? - puppet

I am trying to use an Puppet Augeas resource to update the /etc/fstab file. The change should add a mount option, but ONLY if (a) the option is not already there, and (b) the filesystem is present in fstab.
To do this, I am trying to use an array of onlyif tests, as below:
augeas { "fstab-${fsname}-${setting}":
context => '/files',
lens => 'Fstab.lns',
incl => '/etc/fstab',
onlyif => [
"values etc/fstab/*[file='${fsname}']/opt not_include ${setting}",
"match etc/fstab/*[file='${fsname}'] size > 0",
],
changes => [
"insert opt after etc/fstab/*[file='${fsname}']/opt[last()]",
"set etc/fstab/*[file='${fsname}']/opt[last()] ${setting}"
]
}
If the filesystem exists, the this resource will correctly add the option if it does not exist, and will make no change if the option does exist.
However, if the filesystem does not exist in fstab, then I get an error, as it tries to apply the changes anyway which of course fail as there is no match for the insert.
It appears that the changes are being run even though the match size is returning 0. I can verify that by using augtool:
augtool> ls /files/etc/fstab/*[file='/var/tmp']
augtool> ls /files/etc/fstab/*[file='/tmp']
spec = /dev/mapper/vg_OS-lv_tmp
file = /tmp
vfstype = xfs
opt[1] = defaults
opt[2] = noexec
dump = 0
passno = 0
augtool>
Puppet debug logs do not appear to show the onlyif tests being run at all, but also do not show any syntax errors:
Debug: Augeas[fstab-/var/tmp-noexec](provider=augeas): Opening augeas with root /, lens path /opt/puppetlabs/puppet/cache/lib/augeas/lenses, flags 64
Debug: Augeas[fstab-/var/tmp-noexec](provider=augeas): Augeas version 1.12.0 is installed
Debug: Augeas[fstab-/var/tmp-noexec](provider=augeas): Will attempt to save and only run if files changed
Debug: Augeas[fstab-/var/tmp-noexec](provider=augeas): sending command 'insert' with params ["opt", "after", "/files/etc/fstab/*[file='/var/tmp']/opt[last()]"]
Debug: Augeas[fstab-/var/tmp-noexec](provider=augeas): Closed the augeas connection
Error: /Stage[main]/Smx_os_hardening::Filesystem/Augeas[fstab-/var/tmp-noexec]: Could not evaluate: Error sending command 'insert' with params ["opt", "after", "/files/etc/fstab/*[file='/var/tmp']/opt[last()]"]/Error sending command 'insert' with params ["opt", "after", "/files/etc/fstab/*[file='/var/tmp']/opt[last()]"]
The Puppet documentation appears to indicate that the onlyif can be a single test or an array; and, if an array, the tests must ALL be true (IE, use AND rather than OR). This does not appear to match the current behaviour, though.
[update] Note that, when using onlyif with the Exec resource type, multiple tests do work as expected. I am beginning to think that maybe the Augeas resource does not support an array for onlyif
Does anyone have any insight on what I may be doing wrong here?
Puppet server is v6.18 and so is the agent.

Related

Manage multiple files permissions using puppet

How do I set file permission from list of file names from cat command?
For example, below command returns 3 file names:
$ cat /tmp/test | grep file
/etc/systemd/file_1.log
/etc/systemd/file_2.log
/etc/systemd/file_3.log
How do I use puppet to run the command, get the file names and then loop the 3 file names and set permission accordingly?
The files are resources and if you want to manage a resource you have to know it's there so dynamically created log files are not easy. If you know the file names already then you can use something like this and pass an array into the file resource.
file { ['/etc/systemd/file_1.log',
'/etc/systemd/file_1.log',
'/etc/systemd/file_1.log'] :
ensure => 'file',
mode => '0644',
owner => 'root',
group => 'root',
}
An other method might be to use an exec
exec { 'chmod 644 /etc/systemd/file_*.log':
path => ['/usr/bin', '/usr/sbin',],
}
But you really need something like an onlyif or unless or this is going to execute every 30 minutes and that breaks the idempotent rule we try and apply with Puppet code where things only change if they need correcting. So you're going to need a command line that'll test the permissions and return a boolean to the onlyif.
There are more details here https://puppet.com/docs/puppet/5.5/types/exec.html
A alternative (and the way I'd do it) would be to expose the contents of that file via an external fact which passes the list of files to Puppet to use in the catalog compilation. An external fact can be a bash script so I'd create a file called /etc/facter/facts.d/logfiles.sh, obviously I'd deploy this using Puppet.
#!/usr/bin/env bash
logfiles=($(grep file /tmp/test))
echo "logfiles=${logfiles[*]}"
Then in my Puppet code I'd have something like this;
$logfiles.each |String $logfile| {
file { $logfile :
ensure => 'file',
mode => '0644',
owner => 'root',
group => 'root',
}
}
So when the Puppet run happens the list of log files will be returned to Puppet via the facts and then each file listed is defined as a resource with the correct permissions.
How do I set file permission from list of file names from cat command?
There are two main alternatives, but I observe first that your example is of the output from grep, not cat, and that the cat in that example is superfluous. Nevertheless, those details don't change the big picture -- substantially the same approaches are applicable for data output by any command.
It would be more idiomatic to write a custom fact that captures the filenames (as of the time of each catalog request), and use that information to create the appropriate File resources.
Custom facts are not that hard, but the full details are more than would be appropriate for an SO answer. Supposing that you have a fact $facts['systemd_logs'] whose value is an array of the absolute filenames, you can compactly express the whole group of wanted File resources like so:
file { $facts['systemd_logs']:
mode => '0644',
}
(or whatever mode it is that you want).
It would be quicker (and dirtier) to use an Exec resource to run an appropriate command:
exec { 'ensure correct file permissions':
command => 'chmod 0644 $(/bin/grep file /tmp/test)',
onlyif => '/bin/grep -q file /tmp/test',
provider => 'shell',
}

Puppet: 'creates' does not create a new file

I would like to run a command, only if a file does not exist.
This is what I tried:
exec { 'test':
command => '/usr/bin/echo Test',
creates => '/etc/test/test-init'
}
But unfortunately this file is not being created. The puppet agent works without throwing an error.
The creates parameter of an Exec does not cause Puppet to create the designated file. Rather it instructs Puppet to use the existence of that file to determine whether the command should be run, on the supposition that the command will create it when it runs.
That's often used with commands that naturally create a file or directory, but you can do it synthetically, too. For example:
exec { 'test':
command => '/usr/bin/echo Test && touch /etc/test/test-init',
creates => '/etc/test/test-init',
provider => 'shell'
}

UNIX diff command usuage on puppet exec

I have 2 Release.ini file:
one I am downloading from S3 storing in /CodeRootFolder/tmp location
and other in /DocRootDir location
I want to verify if there are changes in downloaded (i.e. /CodeRootFolder/tmp/Release.ini) file, and if there is change then execute the rsync command via puppet exec as below.
The error is if there change, it does not executing, it seems to as diff returns 1.
exec {'Actual code deployment with rsync':
command => "rsync ${myclass::CodeRootFolder}/tmp/* ${myclass::DocRootDir}/)",
#cwd => "${myclass::CodeRootFolder}",
onlyif => "diff --changed-group-format='%<' --unchanged-group-format='' ${myclass::CodeRootFolder}/tmp/Release.ini ${myclass::DocRootDir}/Release.ini",
path => ['/opt/rh/php55/root/usr/bin','/opt/rh/php55/root/usr/sbin','/usr/local/sbin','/usr/local/bin', '/sbin/' ,'/bin/', '/usr/sbin/','/usr/bin/'],
}
Is there a good solution to my problem.
Thanks in advance
The error is if there change, it does not executing, it seems to as
diff returns 1.
Yes, that's correct.
Invoking diff
An exit status of 0 means no differences were found, 1 means some
differences were found, and 2 means trouble.
If you want the command executed if there is change, you have to use unless instead of onlyif.

Reading from file /etc/resolv.conf and populating in named.conf.options

I'm using puppet to generate my named.conf.options file, in order to do this I'd like it to use the forwarders defined in /etc/resolv.conf. What's the best way of doing this, I've been doing it like this (where named.conf.options.erb contains ) - but this runs constantly.
file { '/etc/bind/named.conf.options':
ensure => present,
content => template('my_template_location/named.conf.options.erb'),
replace => true,
}
->
exec { "add_nameserver":
command => '/bin/sed -i "s/<name_server>/$(grep nameserver/etc/resolv.conf | tr -d [a-z])/g" /etc/bind/named.conf.options',
}
An exec will always run unless it has something to limit it. There are a number of parameters you can set.
In your case, it sounds like you want the exec to run only when your file changes. You might want to use the refreshonly parameter on your exec.
First, change the require arrow to a notify arrow, from -> to ~>. This will cause puppet to refresh the exec whenever the file changes.
Second, add refreshonly => true to your exec. This will cause the exec to only run when it is refreshed by some other resource.
You'll end up with the following:
file { '/etc/bind/named.conf.options':
ensure => present,
content => template('my_template_location/named.conf.options.erb'),
replace => true,
}
~>
exec { "add_nameserver":
command => '/bin/sed -i "s/<name_server>/$(grep nameserver/etc/resolv.conf | tr -d [a-z])/g" /etc/bind/named.conf.options',
refreshonly => true,
}
You can check out some of the other ways to limit an exec on the Puppet Type Reference Page.
You can not get desired state this way because you are modifying the same resource (file /etc/bind/named.conf.options) with two different declarations.
Normally you have to avoid exec resources in Puppet because is difficult to keep state and idempotence while executing "old-school" commands.
So, the best way to get your desired behaviour is to create a custom fact [1] that exposes your nameservers to any resource and then include it in your ERB template.
Facter.add(:nameservers_array) do
setcode do
nameservers = Facter::Core::Execution.exec('grep nameserver/etc/resolv.conf | tr -d [a-z]')
nameservers_array = nameservers.split(" ")
nameservers_array
end
end
You have another example here: https://www.jethrocarr.com/2013/11/05/exposing-name-servers-with-puppet-facts/
[1] https://docs.puppetlabs.com/facter/latest/fact_overview.html

How to check if a directory exists in puppet

Trying to create an autostart directory on a rpi using puppet. It is supposed to mkdir only if the location doesn't exist.
Here is the current code:
exec { "mkdir_autostart":
command => "mkdir /home/pi/.config/autostart",
unless => "[ -d /home/pi/.config/autostart ]",
path => "/usr/local/bin/:/bin/",
}
Here is what I get:
err: Failed to apply catalog: Parameter unless failed on Exec[mkdir_autostart]:
'[ -d /home/pi/.config/autostart ]' is not qualified and no path was specified.
Please qualify the command or specify a path.
Also tried with onlyif statement, but that generated the same error. What am I doing wrong?
EDIT:
Added path (path => "/usr/local/bin/:/bin/",) and now get:
err: /Stage[main]/auto::Sign/Exec[mkdir_autostart]: Could not evaluate: Could not find command '['
You should use the "file" type:
file { "/home/pi/.config/autostart":
ensure => directory
}
But if for any reason, you want to keep your "exec" type, use test -d /home/pi/.config/autostart
Instead of using unless I'd suggest just adding a -p flag to mkdir:
exec { "mkdir_autostart":
command => "mkdir -p /home/pi/.config/autostart",
path => "/usr/local/bin/:/bin/"
}
Or better yet just use Puppet's file resource type as mentioned above by #sebastien.prudhomme.

Resources