Puppet: loops for creating file and symlink simultaneously - puppet

I would like to parse array and create files and symlink in another directory.
I can create files in sites-available and would like to create a symlink in sites-enabled
Could you prompt me on how I can do it, please? Can I do at once?
$domainsnames.each |String $domain| {
file {"/etc/nginx/sites-available/${domain}.conf":
# ensure => link,
content => template('nginx_on_templates/virtualhost.conf.erb'),
# target => "/etc/nginx/sites-enabled/${domain}.conf",
mode => '0644',
owner => 'root',
group => 'root',
}
}
Thank in advance,
Rostyslav

You probably are managing both sites-available and sites-enabled, and you want to simulate what a2ensite enable <site-name> does, correct?
In that case, you have something like:
$domainsnames.each |String $domain| {
file {"/etc/nginx/sites-available/${domain}.conf":
ensure => file,
content => template('nginx_on_templates/virtualhost.conf.erb'),
mode => '0644',
owner => 'root',
group => 'root',
}
file {"/etc/nginx/sites-enabled/${domain}.conf":
ensure => link,
target => "/etc/nginx/sites-available/${domain}.conf",
}
}
I also recommend you taking a look at the puppetlabs/apache module, where you don't have to manage the .conf yourself, but you manage the configurations for your virtual host:
e.g.:
apache::vhost { 'user.example.com':
port => '80',
docroot => '/var/www/user',
docroot_owner => 'www-data',
docroot_group => 'www-data',
}

Related

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!

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

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

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

puppet chown/chmod against files under a directory in batch

In puppet, you can chown/chmod a single file by doing:
file {
'/var/log/mylog/test.log':
ensure => 'present',
mode => '0644',
owner => 'me';
}
Two questions on this:
ensure=>'present' is gonna make sure '/var/log/mylog/test.log' exists, if it doesn't it creates it. Is there any way I can make it do actions if file exists, if file doesn't exist, don't bother to create/delete it, just ignore it and carry on.
Let's say I have 3 files under /var/log/mylog/, I want to chown/chmod against them all in a batch instead of having 3 file resource sections in my puppet code. Can I do something like below(of coz, the code below doesn't exist, it's in my dream now ^_^ ):
files {
'/var/log/mylog/*.log':
ensure => 'present',
mode => '0644',
owner => 'me';
}
If you want to specify to take a given action if file exists, if file doesn't exist etc. you have no choice (to my knownledge) currently than to use the exec resource with creates + onlyif or unless directives.
You could use for instance (see reference doc)
exec { "touch /var/log/mylog/test.log":
path => "/usr/bin:/usr/sbin:/bin",
user => "${yourmodule::params::user}",
group => "${yourmodule::params::group}",
creates => "/var/log/mylog/test.log",
unless => "test -f /var/log/mylog/test.log"
}
file { '/var/log/mylog/test.log':
ensure => 'present',
mode => "${${yourmodule::params::mode}",
owner => "${yourmodule::params::user}",
group => "${yourmodule::params::group}",
require => Exec["touch /var/log/mylog/test.log"]
}
No. Again, you'll have to use an execresource.

Resources