Checking for custom file using puppet - puppet

Part of my puppet manifest checks for the existence of a custom sshd_config. If one is found, I use that. If it's not then I use my default. I'm just wondering if there is a more "puppet" way of doing this
if file("/etc/puppetlabs/puppet/files/${::fqdn}/etc/ssh/sshd_config", '/dev/null') != '' {
$sshd_config_source = "puppet:///private/etc/ssh/sshd_config"
} else {
$sshd_config_source = "puppet:///public/etc/ssh/sshd_config"
}
file { '/etc/ssh/sshd_config':
ensure => 'present',
mode => '600',
source => $sshd_config_source,
notify => Service['sshd'],
}
This code works but it's a little odd as file I have to give it the full path on the puppet master but when assigning $sshd_config_source I have to use the puppet fileserver path (puppet:///private/etc...).
Is there a better way of doing this?

It's a little known feature of the file type that you can supply multiple source values.
From the docs:
Multiple source values can be specified as an array, and Puppet will use the first source that exists. This can be used to serve different files to different system types:
file { "/etc/nfs.conf":
source => [
"puppet:///modules/nfs/conf.$host",
"puppet:///modules/nfs/conf.$operatingsystem",
"puppet:///modules/nfs/conf"
]
}
So you should just specify both the specific and the generic file URL, in that order, and Puppet will do the right thing for you.

Related

Copy overwriting a file only on RPM update

I have following problem with my Puppet installation:
I would like to copy (overwrite) a file only, if a new version of RPM package was installed.
I have something like this:
package { 'my-rpm':
ensure => $version,
source => $rpm_file,
notify => File[$my_file],
}
file { $my_file:
ensure => present,
source => $my_file_template,
replace => true, # Overwrite (default behaviour). Needed in case new RPM was installed.
}
The problem is, that the "file" get executed also, if no new version of RPM was installed. This happens, since I change the $my_file file afterwards using "file_line"
file_line { 'disable_my_service':
ensure => present,
path => $my_file,
line => ' <deployment name="My.jar" runtime-name="My.jar" enabled="false">',
match => ' <deployment name="My.jar" runtime-name="My.jar">',
}
This change of the content of the $my_file triggers copying fresh version from the template on each and every Puppet run.
I could add "repace => false" to my file copy define, but this would break any further updates...
The long story short: I have the following loop
Copy file -> change file -> copy file -> ...
How can I break this loop?
UPDATE:
Clarification:
The "file_line" define is executed optionally, controlled by a Puppet hiera-property and so the "enabled" part can't be included in the RPM.
The entire file can't be turned into a template (IMHO). The problem: Puppet module must be able to install different (future) versions of the file.
The problem remains unsolved for the time being.
I think the problem you're getting here is that you're trying to manage $my_file using both the file and file_line resource types and this is going to cause the file to change during the catalog application.
Pick one or the other, manage it as a template or by file line.
I suspect what's happening here during the Puppet run is the file resource changes $my_file to look like this;
<deployment name="My.jar" runtime-name="My.jar">
Because that's what is in the template then, the file_line resource changes it to;
<deployment name="My.jar" runtime-name="My.jar" enabled="false">
Then on the next run the exact same thing happens, file changes $my_file to match the template and then file_line changes it to modify that line.
I would remove the notify => File[$my_file], it's not actually doing anything, you're defining the desired state in code so if that file changes for any reason, manual change or RPM update, Puppet is going to bring that file back into the desired state during the run. You may want to consider;
file { $my_file:
ensure => present,
source => $my_file_template,
require => Package['my-rpm'],
}
This ensures the file desired state is enforced after the package resource so if the package changes the file the file will be corrected in the same run.
https://puppet.com/docs/puppet/7.4/lang_relationships.html
You may also want to consider;
file { $my_file:
ensure => present,
source => $my_file_template,
require => Package['my-rpm'],
notify => Service['my-service'],
}
So the service provided by the rpm restarts when the config file is changed.
Copy overwriting a file only on RPM update
The problem is, that the "file" get executed also, if no new version of RPM was installed. This happens, since I change the $my_file file afterwards using "file_line"
Yes, File resources in a node's catalog are applied on every run. In fact, it's best to take the view that every resource that makes it into in a node's catalog is applied on every run. Resources' attributes affect what applying them means and / or what it means for them to be in sync, not whether they are applied at all. In the case of File, for example, setting replace => false says that as long as the file initially exists, its content is in sync (and therefore should not be modified), whereas replace => true says that the file's content is in sync only if it is an exact match to the specified source or content.
Generally speaking, it does not work well to manage the same or overlapping physical resources via multiple Puppet resources, and that's what you're running into here. The most idiomatic approach when you run into a problem with that is often to write a custom resource type with which to manage the target object in detail. But in this case, it looks like you could work around the issue by using an Exec to perform the one-time post-update copy:
package { 'my-rpm':
ensure => $version,
source => $rpm_file,
}
~> exec { "Start with default ${my_file}":
command => "cp '${my_file_template}' '${my_file}'",
# this is important:
refreshonly => true,
}
-> file { $my_file:
ensure => 'file',
replace => false,
# no source or content
owner => 'root', # or whatever
group => 'root', # or whatever
mode => '0644',
# ...
}
-> file_line { 'disable_my_service':
ensure => present,
path => $my_file,
# ...
}
You can, of course, use relationship metaparameters instead of the chaining arrows if you prefer or have need.
That approach gives you:
management of the package via the package manager;
copying the packaged default file to the target file only when triggered by the package being updated (by Puppet -- you won't get this if the package is updated manually);
managing properties of the file other than its contents via the File resource; and
managing a specific line of the file's contents via the File_line resource.

Triggering dependent resources in a interation loop

I'm using Puppet to set up workstations and I want to modify the default (NTUSER.DAT) HKLM registry before the user logs on, which involves loading and unloading the hive. I have written some PowerShell scripts to facilitate the load/unload. Although I have three distinct actions, it appears that Puppet is trying to unload the hive before the registry module can make all the changes. I believe I need to add some dependencies using subscribe and refreshonly.
This question is very similar to this one, with the exception that my data is in Hiera, therefore I want to iterate over the data.
$temp_hive_name = $base_windows::temp_hive_name
# LOAD REGISTRY HIVE
exec { 'load_registry_hive' :
command => template('base_windows/Load-RegHive.ps1.erb'),
unless => template('base_windows/Test-HiveLoadState.ps1.erb'),
provider => powershell,
logoutput => true,
}
# MODIFY REGISTRY, ITERATING OVER HIERA DATA
$base_windows::registry.each | $key, $value | {
registry::value { "registry_${key}" :
key => "${value['key']}\\${temp_hive_name}\\${value['subkey']}",
type => $value['type'],
data => $value['data'],
value => $value['value'],
}
}
# UNLOAD REGISTRY HIVE
exec { 'unload_registry_hive' :
command => template('base_windows/Unload-RegHive.ps1.erb'),
onlyif => template('base_windows/Test-HiveLoadState.ps1.erb'),
provider => powershell,
logoutput => true,
}
This works fine when there are one or two Hiera entries.
I guess I could put the load / unload exec resources into an .each loop and add subscribe and refreshonly, however, it seems rather inefficient to do that for each item.
If anyone has any ideas, I'd be grateful if you could share?
T.I.A.
I believe I need to add some dependencies using subscribe and refreshonly.
I'm not so sure that you need to add dependencies, because without explicit dependencies, resources should be applied in the relative order in which they appear in the manifest. Additionally, refreshonly does not declare a dependency, and subscribe is probably not appropriate for this particular task. Furthermore, although refreshonly works in conjunction with dependencies, it's probably not appropriate for this task, either, because notify / subscribe is not right for it.
In a general sense, the key issues are these:
the hive must be loaded before you can attempt to sync any registry entries, so you cannot know whether any given registry resource is out of sync without loading the hive first;
if the hive is loaded then it must also be unloaded;
but the hive must not be unloaded before all the registry entries are synced.
You cannot make Exec['load_registry_hive'] refreshonly because there is no resource that would signal it. You can, however, check whether $base_windows::registry has any elements as a precondition for doing any of the work. If it does, then you definitely need to load the hive.
You can set up explicit dependencies, and I'm generally inclined to do that, as it protects against surprises when a resource is affected by dependency edges that are not apparent at the point of its declaration. So I would suggest this:
$temp_hive_name = $base_windows::temp_hive_name
if ! $base_windows::registry.empty() {
# LOAD REGISTRY HIVE
exec { 'load_registry_hive' :
command => template('base_windows/Load-RegHive.ps1.erb'),
unless => template('base_windows/Test-HiveLoadState.ps1.erb'),
provider => powershell,
logoutput => true,
}
# MODIFY REGISTRY, ITERATING OVER HIERA DATA
$base_windows::registry.each | $key, $value | {
registry::value { "registry_${key}" :
key => "${value['key']}\\${temp_hive_name}\\${value['subkey']}",
type => $value['type'],
data => $value['data'],
value => $value['value'],
require => Exec['load_registry_hive'],
before => Exec['unload_registry_hive'],
}
}
# UNLOAD REGISTRY HIVE
exec { 'unload_registry_hive' :
command => template('base_windows/Unload-RegHive.ps1.erb'),
onlyif => template('base_windows/Test-HiveLoadState.ps1.erb'),
provider => powershell,
logoutput => true,
}
}
Note that you will necessarily both load and unload the hive on each Puppet run, because you cannot determine whether any entries need to be updated without doing so.

Make fact available during puppet catalog compilation

I have two classes in puppet.
Here is the first one:
class nagios::is_production {
file { '/etc/puppetlabs/facter/':
ensure => directory,
}
file { '/etc/puppetlabs/facter/facts.d/':
ensure => directory,
}
file { '/etc/puppetlabs/facter/facts.d/production.txt':
ensure => file,
content => epp('nagios/production.epp')
}
}
This creates a custom fact (production=yes/no based on the node name)
This class on its own assigns the fact correctly.
The second class:
class nagios::client {
if $facts[production] =~ yes {
##nagios_host {"${::hostname}":
ensure => present,
address => $::ipaddress,
hostgroups => "production, all-servers",
notifications_enabled => $notifications_enabled,
use => 'generic-server',
}
} else {
##nagios_host {"${::hostname}":
ensure => present,
address => $::ipaddress,
hostgroups => "non-production, all-servers",
notifications_enabled => $notifications_enabled,
owner => root,
use => 'generic-server',
}
}
}
This creates the exported resource for the host and adds it to either the production/non-production hostgroup.
If the custom fact exists, the host gets created with the hostgroup correctly.
I created a 3rd class to pull in these 2 just to keep track of it a little easier for myself:
class nagios::agent {
Class['nagios::is_production']->Class['nagios::client']
include nagios::is_production
include nagios::client
}
This seems like it should make ::is_production run before ::client. When I include this class on the node for the puppet run, I get this error:
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Left match operand must result in a String value. Got an Undef Value. at /etc/puppetlabs/code/environments/production/modules/nagios/manifests/client.pp:3:6 on node
So the fact seems like it's not getting set causing the export of the host to fail.
What am I missing?
Followup to answer
I am trying to do this:
if domain name contains 'something'
production=yes
else
production=no
Then in the nagios module,
if $facts[production] =~ yes
Assign to production host group.
Bash:
#!/bin/bash
if [[ $(hostname) =~ '512' ]] ; then
echo production=yes
else
echo production=no
fi
Id like to be able to use $facts[something] in here to make create other facts based off things like OS and IP.
I read here: Custom Facts Walkthrough
But I wasn't able to understand the custom facts load path as I didn't have that directory. I'm very new to puppet...
Also new to stack overflow... did I do this right?
Thanks
Facts are generated during pluginsync. Since you are trying to place the external fact during catalog execution, it is not available earlier during catalog compilation, which occurs after pluginsync.
You need to remove your nagios::production class and place your external fact directly in the module to take advantage of pluginsync. It should be located in your module structure like this:
nagios
|__facts.d
|__production.txt
The external fact will then be copied over during pluginsync and the fact will be generated. It will then be available later during catalog compilation. Facter will expect your production.txt to be key:value pairs too.
Check here for more information on properly using external facts: https://docs.puppet.com/facter/3.5/custom_facts.html#external-facts

How to install multiple files with a single File-resource

I need to install a number of files into a directory, which is not itself Puppet-managed. The source of each file is under files/ subdirectory of my module.
I'd like to install them all in one go, because their ownership and permissions are all the same. But what do I put for source? I was hoping, simply specifying the directory would work:
file {[
"${rcdir}/foo", "${rcdir}/bar",
]:
source => "puppet:///${module_name}/",
group => 'wheel',
owner => 'root',
mode => '0644'
}
but, unfortunately, Puppet (using 3.7.5 here) is not smart enough to automatically append the foo and the bar appropriately.
Is there a nice way to do it, or do I have to painstakingly enumerate each file? Thank you!
There are multiple techniques to achieving what you are doing here, with advantages and disadvantages to each.
The first, and most explicit, which gives you the ability to configure each file independently as well as see the complete list of files you are managing, is to define each file independently. To reduce the amount of code duplication, you could utilize type defaults (although this is not always appropriate). This would look something like the following:
File {
group => 'wheel',
owner => 'root',
mode => '0644',
}
file { "${rcdir}/foo":
source => "puppet:///modules/${module_name}/foo",
}
file { "${rcdir}/bar":
source => "puppet:///modules/${module_name}/bar",
}
This obviously becomes very unwieldy quite quickly though.
A second strategy would be to utilize a defined type. It's a bit of a heavy tool to use for something like this, but it'll do the trick. This would look something like this:
define myclass::file_array (
$dest_base,
$source_base,
$group = 'wheel',
$owner = 'root',
$mode = '0644',
) {
file { "${dest_base}/${name}":
source => "${source_base}/${name}",
group => $group,
owner => $owner,
mode => $mode,
}
}
class myclass (){
$files_to_manage = ['foo', 'bar', 'baz']
myclass::file_array { $files_to_manage:
source_base => "puppet:///modules/${module_name}",
dest_base => $rcdir,
}
}
This requires you to add in a relatively arbitrary defined type and ends up requiring you to add lots of other parameters if you want to pass through all the properties available to the core file type, however for your situation it would suffice.
However, the simplest, and cleanest way to do what you are attempting is by allowing the file resource to utilize its recursive functionality, and place all the necessary files into their own directory in your module (assuming you have other files that are unrelated to this destination directory). It does require that you allow Puppet to manage the existence of the directory, but it is difficult to imagine that this is a problem for you (as any of this code would fail if the destination directory didn't exist already anyhow). This would look something like this:
file { $rcdir:
ensure => directory,
recurse => true,
source => "puppet:///modules/${module_name}/rc_files",
owner => 'root',
group => 'wheel',
mode => '0644',
}
// module directory 'files/rc_files' is where foo and bar would exist
I'm pretty sure that last one is your ideal solution, and you can utilize other aspects of the file resource (https://docs.puppet.com/puppet/latest/reference/type.html#file-attribute-recurse) such as purge to validate that no extra files end up in your destination.
There's other techniques out there, but hopefully one of these will do the trick for you.

How to use return value from a Puppet exec?

How can I make the below logic work? My aim is to compare the value of custom fact $environment and the content of the file /etc/facter/facts.d/oldvalue.
If the custom fact $environment is not equal to the content of file /etc/facter/facts.d/oldvalue, then execute the following code.
exec {'catenvchange' :
command => "/bin/cat /root/oldvalue"}
if $environment != exec['catenvchange'] {#code#}
Exec resources do not work that way. In fact, no resource works that way, or any way remotely like that. Moreover, the directory /etc/facter/facts.d/ serves a special purpose, and your expectation for how it might be appropriate to use a file within is not consistent with that purpose.
What you describe wanting to do looks vaguely like setting up an external fact and testing its value. If you drop an executable script named /etc/facter/facts.d/anything by some means (manually, plugin sync, File resource, ...) then that script will be executed before each Puppet run as part of the process of gathering node facts. The standard output generated by the script would be parsed for key=value pairs, each defining a fact name and its value. The facts so designated, such as one named "last_environment" will be available during catalog building. You could then use it like so:
if $::environment != $::last_environment {
# ...
}
Update:
One way to use this mechanism to memorialize the value that a given fact, say $::environment, has on one run so that it can be read back on the next run would be to declare a File resource managing an external fact script. For example,
file { '/etc/facter/facts.d/oldvalues':
ensure => 'file',
owner => 'root',
group => 'root',
mode => '0755',
content => "#!/bin/bash\necho 'last_environment=${::environment}'\n"
}

Resources