Injecting facter into file content - no implicit conversion of Hash into String - puppet

I want to inject some values from facter <prop> into a file content.
It works with $fqdn since facter fqdn returns string.
node default {
file {'/tmp/README.md':
ensure => file,
content => $fqdn, # $(facter fqdn)
owner => 'root',
}
}
However, it does not work with hash object (facter os):
node default {
file {'/tmp/README.md':
ensure => file,
content => $os, # $(facter os) !! DOES NOT WORK
owner => 'root',
}
}
And getting this error message when running puppet agent -t:
Error: Failed to apply catalog: Parameter content failed on
File[/tmp/README.md]: Munging failed for value
{"architecture"=>"x86_64", "family"=>"RedHat", "hardware"=>"x86_64",
"name"=>"CentOS", "release"=>{"full"=>"7.4.1708", "major"=>"7",
"minor"=>"4"}, "selinux"=>{"config_mode"=>"enforcing",
"config_policy"=>"targeted", "current_mode"=>"enforcing",
"enabled"=>true, "enforced"=>true, "policy_version"=>"28"}} in class
content: no implicit conversion of Hash into String (file:
/etc/puppetlabs/code/environments/production/manifests/site.pp, line:
2)
How to convert the hash to string inside the pp file?

If you have Puppet >= 4.5.0, it is now possible to natively convert various data types to strings in the manifests (i.e. in the pp files). The conversion functions are documented here.
This would do what you want:
file { '/tmp/README.md':
ensure => file,
content => String($os),
}
or better:
file { '/tmp/README.md':
ensure => file,
content => String($facts['os']),
}
On my Mac OS X, that leads to a file with:
{'name' => 'Darwin', 'family' => 'Darwin', 'release' => {'major' => '14', 'minor' => '5', 'full' => '14.5.0'}}
Have a look at all that documentation, because there are quite a lot of options that might be useful to you.
Of course, if you wanted the keys inside the $os fact,
file { '/tmp/README.md':
ensure => file,
content => $facts['os']['family'],
}
Now, if you don't have the latest Puppet, and you don't have the string conversion functions, the old way of doing this would be via templates and embedded Ruby (ERB), e.g.
$os_str = inline_template("<%= #os.to_s %>")
file { '/tmp/README.md':
ensure => file,
content => $os_str,
}
This actually leads to a slightly differently-formatted Hash since Ruby, not Puppet does the formatting:
{"name"=>"Darwin", "family"=>"Darwin", "release"=>{"major"=>"14", "minor"=>"5", "full"=>"14.5.0"}}

Related

Puppet: set default values for nested hashes

I am creating a class which creates a ssh keys pair for users.
In Hiera it looks like this:
ssh::default_user_keys::entries:
root:
privkey: ENC[PKCS7,MIIH/QYJKoZIhvcNAQcDoIIH7jCCB+oCAQAx...]
pubkey: BiQYJKoZIhvcNAQcDoIIBejCCAXYC...
user1:
privkey: ENC[PKCS7,MIIH/Bf0mv0aa7JRbEWWONfVMJBOX2I7x...]
keydir: /srv/data/user1/.ssh
user2:
privkey: ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAx...]
keytype: id_ecdsa
If some values are not specified in Hiera, they must be picked up from default values in the manifest.
So my question is how to write the manifest correctly?
This is my pseudo code I try to make working:
class ssh::default_user_keys_refact (
Hash $entries = lookup('ssh::default_user_keys_refact::entries', "merge" => 'hash'),
) {
$entries.each |String $user, Hash $item = {} | {
set_default { $item:
privkey => undef,
pubkey => undef,
keydir => "/home/${user}/.ssh",
keytype => id_rsa,
comment => undef,
}
$sshpriv = $item[privkey]
file { "${keydir}/$[keytype}":
ensure => file,
owner => $user,
mode => '0600',
content => $sshpriv,
}
}
}
Do you have ideas how to make this pseudo code working? Or in other words, how to set up default values for nested hiera hash in puppet manifest?
puppet versions is 6.
Thank you in advance.
Or in other words, how to set up default values for nested hiera hash in puppet manifest?
The fact that the hashes for which you want to provide defaults are nested inside others in the Hiera data isn't much relevant, because at the point of use they have been plucked out. With that being the case, you can accomplish this fairly easily by leveraging Puppet (not Hiera) hash merging. It might look something like this:
$entries.each |String $user, Hash $props| {
# Merge with default values
$full_props = {
'keydir' => "/home/${user}/.ssh",
'keytype' => 'id_rsa'
} + $props
file { "${full_props['keydir']}/${full_props['keytype']}":
ensure => 'file',
owner => $user,
mode => '0600',
content => $full_props['privkey'],
}
}
That's by no means the only way, but it's clean and clear, and it scales reasonably well.

Puppet - how to use different file source

I created a configuration in Puppet for Nagios agent (NRPE). Now, I'm trying to set different file sources depending on the existence of dirs. First, I check if a specific directory exists and then set specific file content. My current config files look like:
class nagios_client::file_nagios-check-Linux-stats {
include nagios_client::check_location_lib-nagios
file { '/usr/lib/nagios/plugins/check_linux_stats.pl':
ensure => file,
owner => root,
group => root,
mode => 755,
content => template("nagios_client/check_linux_stats.pl.erb"),
require => Exec["check_usr-lib_exists"],
}
file { '/usr/lib64/nagios/plugins/check_linux_stats.pl':
ensure => file,
owner => root,
group => root,
mode => 755,
content => template("nagios_client/check_linux_stats.pl.erb"),
require => Exec["check_usr-lib64_exists"],
}
file { '/usr/lib32/nagios/plugins/check_linux_stats.pl':
ensure => file,
owner => root,
group => root,
mode => 755,
content => template("nagios_client/check_linux_stats.pl.erb"),
require => Exec["check_usr-lib32_exists"],
}
}
This works fine, but I have a problem with this:
class nagios_client::file_nrpe-cfg {
# include nagios_client::check_location_lib-nagios
file { '/etc/nagios/nrpe.cfg.def':
path => '/etc/nagios/nrpe.cfg',
ensure => file,
owner => root,
group => root,
mode => 644,
content => template("nagios_client/nrpe-cfg.erb"),
require => Exec["check_usr-lib_exists"],
}
file { '/etc/nagios/nrpe.cfg.32':
path => '/etc/nagios/nrpe.cfg',
ensure => file,
owner => root,
group => root,
mode => 644,
content => template("nagios_client/nrpe-cfg-32.erb"),
require => Exec["check_usr-lib32_exists"],
}
file { '/etc/nagios/nrpe.cfg.64':
path => '/etc/nagios/nrpe.cfg',
ensure => file,
owner => root,
group => root,
mode => 644,
content => template("nagios_client/nrpe-cfg-64.erb"),
require => Exec["check_usr-lib64_exists"],
}
}
It looks like require => Exec[...] is ignored.
My check definition:
class nagios_client::check_location_lib-nagios {
exec { 'check_usr-lib_exists':
command => '/bin/true',
onlyif => '/usr/bin/test -d /usr/lib/nagios/plugins',
}
exec { 'check_usr-lib32_exists':
command => '/bin/true',
onlyif => '/usr/bin/test -d /usr/lib32/nagios/plugins',
}
exec { 'check_usr-lib64_exists':
command => '/bin/true',
onlyif => '/usr/bin/test -d /usr/lib64/nagios/plugins',
}
}
I'm using Puppet 3.8.7. How to do it in the right way?
The problem with what you have is that you are using require, which only makes sure that the specified resource (in this case each exec) executes before the file resource. The behavior you want corresponds more closely to notify relationships (which create a refresh event), however, file resources do not care about refresh events. You can read more about refresh relationships here: https://puppet.com/docs/puppet/latest/lang_relationships.html#refreshing-and-notification.
There are 2 possible ways I can think of fixing this. The first one would be to use an exec statement to manage the file, instead of a file resource. This is definitely not optimal, since you lose all of the parameters from the file resource (I definitely do not recommend this approach, but you could).
The other way would be to create a custom ruby fact to check if the files exist. The fact would look something like this:
Facter.add('nagios_directories') do
confine kernel: 'Linux'
setcode do
paths_to_check = [
'/usr/lib/nagios/plugins',
'/usr/lib32/nagios/plugins',
'/usr/lib64/nagios/plugins',
]
paths_to_check.select { |d| File.directory?(d) }
end
end
This fact would check all the directories listed in the paths_to_check array, and return an array containing the directories that do exist. If none of the directories exist, it would return an empty array.
Once you have that fact set up, you can then rewrite your code like this:
class nagios_client::file_nrpe-cfg {
if (member($fact['nagios_directories'], '/usr/lib/nagios/plugins')) {
file { '/etc/nagios/nrpe.cfg.def':
path => '/etc/nagios/nrpe.cfg',
ensure => file,
owner => root,
group => root,
mode => 644,
content => template("nagios_client/nrpe-cfg.erb"),
}
}
if (member($fact['nagios_directories'], '/usr/lib32/nagios/plugins')) {
file { '/etc/nagios/nrpe.cfg.32':
path => '/etc/nagios/nrpe.cfg',
ensure => file,
owner => root,
group => root,
mode => 644,
content => template("nagios_client/nrpe-cfg-32.erb"),
}
}
if (member($fact['nagios_directories'], '/usr/lib64/nagios/plugins')) {
file { '/etc/nagios/nrpe.cfg.64':
path => '/etc/nagios/nrpe.cfg',
ensure => file,
owner => root,
group => root,
mode => 644,
content => template("nagios_client/nrpe-cfg-64.erb"),
}
}
}
Here is some additional documentation for custom facts: https://puppet.com/docs/facter/3.9/fact_overview.html.
Lastly, if you are using Puppet 6 (currently the latest release), you can write a custom Ruby function and make use of the new deferred type. This type allows you to execute functions on the agent during catalog run time (before this release, all Puppet functions where executed on the Puppet Master at compile time), which means you can use a function to check if a file exists. I have not had a chance to try this feature, but you can view the documentation here: https://puppet.com/docs/puppet/6.0/integrating_secrets_and_retrieving_agent-side_data.html.

how to use foreman query in puppet manifest

I have foreman query, which get puppet nodes from Foreman hostgroup.
I want use output from this query in my puppet manifest in nodes section.
My foreman query:
$query = foreman({foreman_user => 'admin',
foreman_pass => 'pass',
item => 'hosts',
search => 'hostgroup_fullname ~ web servers',
filter_result => ['certname'],
})
I want use "${_query}" variable in nodes class
Something like this:
node "${_query}" {
file { '/tmp/testdir':
ensure => 'directory',
}
file { '/tmp/testdir/testfile2':
ensure => present,
content => 'this is test file in directory',
owner => 'root',
group => 'root'
}
}
In normal Puppet manifest I must use this multiple nodes sintax:
node 'www1.example.com', 'www2.example.com', 'www3.example.com' {
file { '/tmp/testdir':
ensure => 'directory',
}
file { '/tmp/testdir/testfile2':
ensure => present,
content => 'this is test file in directory',
owner => 'root',
group => 'root'
}
}
When I execute my manifest with my foreman query I got this error:
Server Error: Could not parse for environment production: An interpolated expression is not allowed in a hostname of a node
I Understand the problem is in my foreman query variable.
How I must change my foreman query to use in myltiple nodes section in my manifest file?
Thanks in advance!

Puppet : how to resolve "file_line" multi match patterns

I'm trying to change configuration file using puppet.
This is my test.txt file that i want to change :
[default]
#puppet=no
abc=123
[nova]
#puppet=no
I want to change "#puppet=no" to "puppet=yes" only on [default] tab.
This is my test.pp for two version :
file_line{"someline":
path => '/root/openstack-puppet/computenode/nova/test.txt',
match => '[default]\n#puppet',
line => 'puppet=ok'
}
This one failed to find match pattern, so it just add "puppet=ok" at the end of file.
file_line{"someline":
path => '/root/openstack-puppet/computenode/nova/test.txt',
match => '#puppet',
line => 'puppet=ok'
}
This one failed because of multi match pattern problem.
I tried Augeas also, but I can't find how to uncomment using Augeas.
Somebody please help me with this problem!!
=========================================================================
I run this code :
file_line { 'someline':
path => '/root/openstack-puppet/computenode/nova/test.txt',
after => '\[default\]',
multiple => 'false',
match => '#puppet',
line => 'puppet=ok',
}
But when I run with "puppet apply" it still makes same error :
Error: More than one line in file '/root/openstack-puppet/computenode/nova/test.txt' matches pattern '#puppet'
Error: /Stage[main]/Main/File_line[someline]/ensure: change from absent to present failed: More than one line in file '/root/openstack-puppet/computenode/nova/test.txt' matches pattern '#puppet'
I think that 'after' attribute cannot applied when 'match' attribute is defined.
When I erase 'match' attribute, it works, but it didn't replace original string('#puppet=no').
It just added new line after [default] like this :
[default]
puppet=ok
#puppet=no
abc=123
dedd=0
[nova]
#puppet=no
So the issues still remain, how can I erase(or replace) the string '#puppet=no'
only on [default] tab??
The after attribute will solve this problem for you. Taking your second resource and cleaning up some, we have:
file_line { 'someline':
path => '/root/openstack-puppet/computenode/nova/test.txt',
match => '#puppet',
line => 'puppet=ok'
after => '[default]',
multiple => false,
}
Notice I also added the multiple attribute to safeguard against changing more than just the line you want to change.
The reason your first resource would have issues is threefold. First, file_line requires that your line attribute have a successful regexp match against the match attribute, which is not true in your case. Second, putting [default] in the match attribute means that [default] would be removed from your file if the resource succeeded as you wrote it. Third, you need to escape [] in your regexp, so it would look like \[default\] if you wanted to go that route (and you do not for the first two reasons given).
The file looks like it fits with the ini file format so a better solution would be to use the inifile resource type https://forge.puppet.com/modules/puppetlabs/inifile
ini_setting { "sample setting":
ensure => present,
path => '/root/openstack-puppet/computenode/nova/test.txt',
section => 'default',
setting => 'puppet',
value => 'yes',
}
Hi you can try it.
include stdlib
file_line{"someline":
ensure => 'present',
after => 'default',
multiple => false,
path => '/root/openstack-puppet/computenode/nova/test.txt',
line => 'puppet=ok',
}

Adding custom server options with puppetlabs mysql module

I have a puppet manifest that contains this phrase (my question is about the production block at the end), used to set up mysql configuration files:
class web_mysql_server {
include web_mysql::packages
include web_mysql::mysql_server
}
class web_mysql::packages {
Package { ensure => installed }
package { 'libmysqlclient-dev': }
}
class web_mysql::mysql_server {
# Qualified keys (dot notation: web_mysql_server.mysql_database) isn't available
# until hiera 2.0.
$mysql_config = hiera('web_mysql_server')
# https://forge.puppetlabs.com/puppetlabs/mysql
class { '::mysql::server':
root_password => '...',
remove_default_accounts => true,
override_options => {
# The data in this block gets written to /etc/mysql/my.cnf.
'mysqld' => {
max_connections => '1024', # No good reason but parroting others.
key_buffer_size => '512M', # No good reason but parroting others.
},
'production' => {
adaptor => 'mysql2',
database => $mysql_config['mysql_database'],
user => $mysql_config['mysql_username'],
password => $mysql_config['mysql_password'],
host => '0.0.0.0',
encoding => 'UTF8',
},
But sometimes I'd maybe like the database to be a staging database, and so it's a bit confusing to call it production. No problem, I've recently learned about hiera, it's my hammer for all nails today:
$mysql_config['mysql_name'] => {
adaptor => 'mysql2',
database => $mysql_config['mysql_database'],
user => $mysql_config['mysql_username'],
password => $mysql_config['mysql_password'],
host => '0.0.0.0',
encoding => 'UTF8',
},
No, that doesn't work, though the error ("syntax error at line (...with '=>'...), expected '}'") isn't terribly revealing.
("Key" might be the wrong word. Please correct me. I'm a bit new to puppet. And that itself may be why I've not been able to answer this question by googling and reading.)
Thanks for any pointers.
Update: I see now that the mysql module provides an option to add section to the config file out of the box. Here is how it should be used according to the documentation:
$override_options = {
$mysql_config['mysql_name'] => {
adaptor => 'mysql2',
database => $mysql_config['mysql_database']
# ...
}
}
class { '::mysql::server':
root_password => 'strongpassword',
remove_default_accounts => true,
override_options => $override_options,
}
Or you can save some code if you put the mysql_name value under a different hiera key:
$override_options = {
hiera('mysql_name') => hiera('web_mysql_server')
}
Original answer:
If your actual intention is just to create a configuration file, the best way is using templates in erb syntax.
Here is an example. First, you put a my.cnf.erb file to the templates directory of your module (e.g. modules/web_mysql/templates):
[<%= #mysql_config['mysql_name'] %>]
adaptor = mysql2
database = <%= #mysql_config['mysql_database'] %>
user = <%= #mysql_config['mysql_username'] %>
password = <%= #mysql_config['mysql_password'] %>
host = 0.0.0.0
encoding = UTF8
You can use this template in your web_mysql::mysql_server class to create the config file:
class web_mysql::mysql_server {
# ...
$mysql_config = hiera('web_mysql_server')
file { '/etc/mysql/my.cnf':
ensure => 'present',
content => template('web_mysql/my.cnf.erb'),
}
}
The way you're using web_mysql::mysql_server indicates that the class has a parameter called production. If that is the case you have to use that name when you use the class. Instead, why not just call the parameter mysql or client, since it seems to be the client configuration parameter?
Answer to original question:
Neither version of your manifest is valid Puppet. For example, putting this in my manifest:
'production' => {
adaptor => 'mysql2',
}
and running puppet apply --noop --modulepath modules manifests/host.pp results in:
Error: Could not parse for environment production: Syntax error at '=>' at [...]/host.pp:1:14 on node hostname
Did you mean to say something like this?
$database_configuration = {
adaptor => 'mysql2',
database => $mysql_config['mysql_database'],
user => $mysql_config['mysql_username'],
password => $mysql_config['mysql_password'],
host => '0.0.0.0',
encoding => 'UTF8',
}

Resources