Manage multiple files permissions using puppet - 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',
}

Related

How to comment all the uncommented lines in a file using puppet module

I have a sshd_config configuration file which contains commented as well as uncommented lines. I want to comment all the uncommented lines in that file using puppet. Is there any optimal/simple way to do this? Or is there a way to run bash command (maybe sed to replace) via puppet? I am not sure that using bash command is a right approach.
It would be really helpful is someone guides me with this. Thanks in advance!
Is there any optimal/simple way to do this?
There is no built-in resource type or well-known module that specifically ensures that non-blank lines of a file start with a # character.
Or is there a way to run bash command (maybe sed to replace) via puppet?
Yes, the Exec resource type. That's your best bet short of writing a custom resource type.
I am not sure that using bash command is a right approach.
In a general sense, it's not. Appropriate, specific resource types are better than Exec. But when you don't have a suitable one and can't be bothered to make one, Exec is available.
It might look like this:
# The file to work with, so that we don't have to repeat ourselves
$target_file = '/etc/ssh/sshd_config'
exec { "Comment uncommented ${target_file} lines":
# Specifying the command in array form avoids complicated quoting or any
# risk of Puppet word-splitting the command incorrectly
command => ['sed', '-i', '-e', '/^[[:space:]]*[^#]/ s/^/# /', $target_file],
# If we didn't specify a search path then we would need to use fully-qualified
# command names in 'command' above and 'onlyif' below
path => ['/bin', '/usr/bin', '/sbin', '/usr/sbin'],
# The file needs to be modified only if it contains any non-blank, uncommented
# lines. Testing that via an 'onlyif' ensures that Puppet will not
# run 'sed' or (more importantly) report the file changed when it does
# not initially contain any lines that need to be commented
onlyif => [['grep', '-q', '^[[:space:]]*[^#]', $target_file]],
# This is the default provider for any target node where the rest of this
# resource would work anyway. Specifying it explicitly will lead to a more
# informative diagnostic if there is an attempt to apply this resource to
# a system to which it is unsuited.
provider => 'posix',
}
That does not rely on bash or any other shell to run the commands, but it does rely on sed and grep being available in one of the specified directories. In fact, it relies specifically on GNU sed or one that supports an -i option with the same semantics. Notably, that does not include BSD-style sed, such as you will find on macOS.

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'
}

Copying a file only once in puppet

I am new to puppet, I am trying to copy a file to remote server for installation of a package, This file would get deleted a few minutes after installation. Is there a way to copy this file only once.? like the example below.
class absent_file {
file { '/tmp/hello-file':
ensure => 'present',
replace => 'no',
content => "From Puppet\n",
mode => '0644',
once => true
}
}
So, I have figured out how to do it. Instead of using the file module, I ended up using command and touching another which would not get deleted along with installation process. The key can be deleted but the somefile.txt would be still present and the process would become idempotent.
exec {'add key':
command => "echo 'SharedKeyfile' > /key/location && touch /key/location/somefile.txt'",
onlyif => "test ! -f /key/location/somefile.txt",
}

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

Need some help in appending more than one line to a file in puppet

Using this manifest
file_line { 'sudo_rule':
path => '/etc/sudoers',
line => '%sudo ALL=(ALL) ALL',
}
Puppet adds a line, but I want more than one line to append to a file.
File_line only
Ensures that a given line is contained within a file. (according to the doc)
So you may add more lines to your puppet configuration file and it will work out of the box since puppet only ensures that they are there but doesn't add them continuously.
Maybe you tell us what your usecase is so that we can help you better. Another option would also be augeas to load config files into a tree like datastructure and edit the appropriate values in place.
Example usage:
augeas { "sshd_config":
context => "/files/etc/ssh/sshd_config",
changes => [
"set PermitRootLogin no",
],
}

Resources