Make fact available during puppet catalog compilation - puppet

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

Related

How can I use Foreman host groups with Puppet?

I have this manifest:
$foremanlogin = file('/etc/puppetlabs/code/environments/production/manifests/foremanlogin.txt')
$foremanpass = file('/etc/puppetlabs/code/environments/production/manifests/foremanpass.txt')
$query = foreman({foreman_user => "$foremanlogin",
foreman_pass => "$foremanpass",
item => 'hosts',
search => 'hostgroup = "Web Servers"',
filter_result => 'name',
})
$quoted = regsubst($query, '(.*)', '"\1"')
$query6 = join($quoted, ",")
notify{"The value is: ${query6}": }
node ${query6} {
package { 'atop':
ensure => 'installed',
}
}
When I execute this on agent I got error:
Server Error: Could not parse for environment production: Syntax error at ''
Error in my node block
node ${query6} {
package { 'atop':
ensure => 'installed',
}
}
I see correct output from notify, my variable looks like this:
"test-ubuntu1","test-ubuntu2"
Variable in correct node manifest format.
I don't understand whats wrong? variable query6 is correct.
How to fix that?
I just want to apply this manifest to foreman host group, how to do this right?
On the Puppet side, you create classes describing how to manage appropriate subunits of your machines' overall configuration, and organize those classes into modules. The details of this are far too broad to cover in an SO answer -- it would be analogous to answering "How do I program in [language X]?".
Having prepared your classes, the task is to instruct Puppet which ones to assign to each node. This is called "classification". Node blocks are one way to perform classification. Another is external node classifiers (ENCs). There are also alternatives based on ordinary top-level Puppet code in your site manifest. None of these are exclusive.
If you are running Puppet with The Foreman, however, then you should configure Puppet to use the ENC that Foreman provides. You then use Foreman to assign (Puppet) classes to nodes and / or node groups, and Foreman communicates the details to Puppet via its ENC. That does not require any classification code on the Puppet side at all.
See also How does host groups work with foreman?

Puppet: Dependency chain not getting executed in order

I have an issue wherein i am trying to set external facts and then copy a template file which gets populated with values from hiera yaml file. The template file is dependent on certain facts(such as the owner and group of the of the template file) which gets set by the external facts file. Below is the puppet code.
file {['/etc/facter/','/etc/facter/facts.d']:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}
file {"/etc/facter/facts.d/domain_facts.sh":
ensure => present,
owner => 'root',
group => 'root',
mode => '0755',
source => $::hostname?{
'hostname1' => 'puppet:///modules/vfecare/hostname1.sh',
},
require => File['/etc/facter/','/etc/facter/facts.d'],
}
file {"/tmp/testing123":
ensure => present,
owner => "${::remoteuser}",
group => "${::remotegroup}",
content => template("vfecare/testscript.erb"),
require => File["/etc/facter/facts.d/domain_facts.sh"]
}
However during execution, i see that the template gets copied first to the puppet agent machine and since the template has some values that it needs from the external facts file, it cannot find and it throws error saying "Invalid owner and group value".
Below is the content of the external facts file
#!/bin/bash
echo "remoteuser=tempuser"
echo "remotegroup=tempuser"
Why does puppet seem to ignore the dependency cycle here?
Facts are collected by the agent at the very start of a Puppet run, before the catalog containing your file resources gets executed. It isn't possible to deploy an external fact during the run and use it like this as the facts will be missing.
Instead, you need to rely on Puppet's "pluginsync" mechanism that copies external facts from the master to the agent before it collects facts.
Move the vfecare/files/hostname1.sh fact file in the module to vfecare/facts.d/hostname1.sh, remove the file resources you have for /etc/facter and copying the external fact, then re-run the agent. It should first download the hostname1.sh fact file, then evaluate the /tmp/testing123 file resource correctly with the remoteuser/group values.
See the docs at Auto-download of agent-side plugins for more information.

Checking for custom file using 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.

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

Missing resources when running "puppet agent --noop"

I may have misunderstood how "puppet agent --noop" works:
In the definition of a class I set the existence of a file and I set it's user&group ownership and this is what I have when I un "puppet agent --noop" :
If the file doesn't exist, "puppet agent --noop" works fine
If the file exists but user or group doesn't exist, then "puppet agent --noop" fails
complaining about the missing user or group.
If I simply run "puppet agent" (without "--noop") it works fine: Doesn't
matter if the user, group or file exists or not previously: it
creates the group, the user and/or the file.
1st question: I suppose that the "--noop" run doesn't verify if the catalog is asking the missing resources to be created. Isn't it?
2nd question: Is there any way to do any kind of mocking to avoid the problem of missing resources when launching "--noop"?
Let's paste some code to show it:
# yes, it should better be virtual resources
group { $at_group:
ensure => "present"
}
user { $at_user:
ensure => present,
gid => "$at_group",
require => Group[$at_group],
}
file { '/etc/afile':
owner => $at_user,
group => $at_group,
mode => '0440',
content => template('......erb')
require => User[$at_user]
}
output:
# puppet agent --test --noop
Info: Retrieving plugin
Info: Loading facts in /var/lib/puppet/lib/facter/puppet_vardir.rb
Info: Loading facts in /var/lib/puppet/lib/facter/facter_dot_d.rb
Info: Loading facts in /var/lib/puppet/lib/facter/pe_version.rb
Info: Loading facts in /var/lib/puppet/lib/facter/root_home.rb
Info: Caching catalog for pagent02
Info: Applying configuration version '1403055383'
Notice: /Stage[main]/Agalindotest::Install/Group[my_group]/ensure: current_value absent, should be present (noop)
Notice: /Stage[main]/Agalindotest::Install/User[my_user]/ensure: current_value absent, should be present (noop)
Error: Could not find user my_user
Error: /Stage[main]/Agalindotest::Install/File[/etc/afile]/owner: change from 1001 to my_user failed: Could not find user my_user
Error: Could not find group my_group
Error: /Stage[main]/Agalindotest::Install/File[/etc/afiles]/group: change from 1001 to my_group failed: Could not find group my_group
Let's show how it works if the file doesn't exist:
then "puppet agent --test --noop" works like a charm:
Notice: /Stage[main]/Agalindotest::Install/Group[my_group]/ensure: current_value absent, should be present (noop)
Notice: /Stage[main]/Agalindotest::Install/User[my_user]/ensure: current_value absent, should be present (noop)
Notice: /Stage[main]/Agalindotest::Install/File[/etc/afile]/ensure: current_value absent, should be file (noop)
Thanks a lot!!
/ Angel
Unfortunately, there is currently no way to overcome this limitation.
The ensure property doesn't fail just on account of a missing owner - I believe the file will just end up owned by root. That is why the output is more pleasant when the file doesn't exist.
As for the behavior with an existing file: Each resource is considered individually, and the file resource must admit failure if the group does not exist when the file is evaluated. The fact that the group would (likely) be created without noop cannot be easily accounted for.
As for you idea of ignoring the issue under noop conditions if there is a user resource - that has merit, I believe. Would you raise that as a feature request at Puppet's Jira?
Update
As of Puppet 3.3 you can use rely on the $clientnoop value that is supplied by the agent along with Facter facts. Please note that tailoring your manifest to avoid failures in noop mode has two consequences.
The manifest itself becomes much less maintainable and comprehendible.
The reporting from noop runs becomes inaccurate, because the "unsafe" property values are not part of the noop catalog
You could build the manifest like this:
# this scenario does not actually call for virtual resources at all :-)
group { $at_group:
ensure => "present"
}
user { $at_user:
ensure => present,
gid => "$at_group",
require => Group[$at_group],
}
file { '/etc/afile':
mode => '0440',
content => template('......erb')
# require => User[$at_user] # <- not needed at all, Puppet autorequires the user and group
}
if ! $::clientnoop {
File['/etc/afile'] {
owner => $at_user,
group => $at_group,
}
}
The owner and group properties are ignored in noop mode, with the pros and cons as discussed above.
All things considered, I feel that this is not worth the hassle at all.

Resources